├── .DS_Store ├── .gitignore ├── LICENSE ├── README.md ├── data └── kaggle_survey_2018.csv ├── images ├── RStudio.png ├── Rlogo.png ├── github_repo_download.png ├── hadley_data_science.png ├── number-of-submitted-packages-to-CRAN.png ├── ppf_success.gif ├── r-bloggers_logo.png └── r4ds_had_cover.png ├── libs ├── Proj4Leaflet │ ├── proj4-compressed.js │ ├── proj4.js │ └── proj4leaflet.js ├── crosstalk │ ├── css │ │ └── crosstalk.css │ └── js │ │ ├── crosstalk.js │ │ ├── crosstalk.js.map │ │ ├── crosstalk.min.js │ │ └── crosstalk.min.js.map ├── datatables-binding │ └── datatables.js ├── datatables-css │ └── datatables-crosstalk.css ├── dt-core │ ├── css │ │ ├── jquery.dataTables.extra.css │ │ └── jquery.dataTables.min.css │ └── js │ │ └── jquery.dataTables.min.js ├── htmlwidgets │ └── htmlwidgets.js ├── jquery │ ├── LICENSE.txt │ └── jquery.min.js ├── leaflet-binding │ └── leaflet.js ├── leaflet │ ├── images │ │ ├── layers-2x.png │ │ ├── layers.png │ │ ├── marker-icon-2x.png │ │ ├── marker-icon.png │ │ └── marker-shadow.png │ ├── leaflet-src.js │ ├── leaflet-src.js.map │ ├── leaflet.css │ ├── leaflet.js │ └── leaflet.js.map ├── leafletfix │ └── leafletfix.css ├── remark-css │ ├── default-fonts.css │ └── default.css ├── remark-latest.min.js └── rstudio_leaflet │ ├── images │ └── 1px.png │ └── rstudio_leaflet.css ├── presentation.Rmd ├── presentation.html ├── presentation.pdf ├── presentation_files └── figure-html │ ├── countries2 -1.png │ ├── countries2-1.png │ ├── countries3 -1.png │ ├── countries3-1.png │ └── iechoFALSE-1.png ├── projects ├── .DS_Store └── Object_Detection_in_R │ ├── LICENSE │ ├── README.html │ ├── README.md │ ├── google-car.png │ └── tinyyolo_3_lines_in_R.R └── r_beginners_workshop.Rproj /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrrs/r_beginners_workshop/1971ded361f43811a542583a43a0e82fd256642e/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # R Beginner's Workshop 2 | 3 | ## Instructions 4 | 5 | * **Download and Install [R](https://cloud.r-project.org/)** 6 | * **Download and Install [RStudio Desktop](https://www.rstudio.com/products/rstudio/download/)** 7 | * **Install the Following Packages** 8 | * tidyverse - `install.packages('tidyverse')` (**Required**) 9 | * scales - `install.packages('scales')` 10 | * ggthemes - `install.packages('ggthemes')` 11 | * **Clone/Download** this Repo and Save it in your Local Machine (if it's a .zip file, please unzip and save it) 12 | 13 | 14 | 15 | * Please keep in mind, This Repo contains a Dataset of 40ish MB Size (You are free to use R's in-built datasets in the workshop) 16 | 17 | About the Dataset: 18 | 19 | * [Kaggle Survey 2018 Dataset](https://www.kaggle.com/kaggle/kaggle-survey-2018/) 20 | 21 | Project Demo: 22 | 23 | * [Object Detection in R using Tiny YOLO](https://heartbeat.fritz.ai/object-detection-in-just-3-lines-of-r-code-using-tiny-yolo-b5a16e50e8a0) 24 | 25 | 26 | ### [Presentation Link - HTML](https://amrrs.github.io/r_beginners_workshop/presentation.html) 27 | 28 | ### [Presentation PDF](https://github.com/amrrs/r_beginners_workshop/blob/master/presentation.pdf) 29 | 30 | # Re-Rendering the Presentation 31 | 32 | * Install [`xaringan`](https://github.com/yihui/xaringan) R package 33 | * Open `presentation.Rmd` and `Knit` the Rmarkdown file 34 | * Rendered `presentation.html` would be saved in the project folder 35 | 36 | -------------------------------------------------------------------------------- /images/RStudio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrrs/r_beginners_workshop/1971ded361f43811a542583a43a0e82fd256642e/images/RStudio.png -------------------------------------------------------------------------------- /images/Rlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrrs/r_beginners_workshop/1971ded361f43811a542583a43a0e82fd256642e/images/Rlogo.png -------------------------------------------------------------------------------- /images/github_repo_download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrrs/r_beginners_workshop/1971ded361f43811a542583a43a0e82fd256642e/images/github_repo_download.png -------------------------------------------------------------------------------- /images/hadley_data_science.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrrs/r_beginners_workshop/1971ded361f43811a542583a43a0e82fd256642e/images/hadley_data_science.png -------------------------------------------------------------------------------- /images/number-of-submitted-packages-to-CRAN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrrs/r_beginners_workshop/1971ded361f43811a542583a43a0e82fd256642e/images/number-of-submitted-packages-to-CRAN.png -------------------------------------------------------------------------------- /images/ppf_success.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrrs/r_beginners_workshop/1971ded361f43811a542583a43a0e82fd256642e/images/ppf_success.gif -------------------------------------------------------------------------------- /images/r-bloggers_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrrs/r_beginners_workshop/1971ded361f43811a542583a43a0e82fd256642e/images/r-bloggers_logo.png -------------------------------------------------------------------------------- /images/r4ds_had_cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrrs/r_beginners_workshop/1971ded361f43811a542583a43a0e82fd256642e/images/r4ds_had_cover.png -------------------------------------------------------------------------------- /libs/Proj4Leaflet/proj4leaflet.js: -------------------------------------------------------------------------------- 1 | (function (factory) { 2 | var L, proj4; 3 | if (typeof define === 'function' && define.amd) { 4 | // AMD 5 | define(['leaflet', 'proj4'], factory); 6 | } else if (typeof module === 'object' && typeof module.exports === "object") { 7 | // Node/CommonJS 8 | L = require('leaflet'); 9 | proj4 = require('proj4'); 10 | module.exports = factory(L, proj4); 11 | } else { 12 | // Browser globals 13 | if (typeof window.L === 'undefined' || typeof window.proj4 === 'undefined') 14 | throw 'Leaflet and proj4 must be loaded first'; 15 | factory(window.L, window.proj4); 16 | } 17 | }(function (L, proj4) { 18 | if (proj4.__esModule && proj4.default) { 19 | // If proj4 was bundled as an ES6 module, unwrap it to get 20 | // to the actual main proj4 object. 21 | // See discussion in https://github.com/kartena/Proj4Leaflet/pull/147 22 | proj4 = proj4.default; 23 | } 24 | 25 | L.Proj = {}; 26 | 27 | L.Proj._isProj4Obj = function(a) { 28 | return (typeof a.inverse !== 'undefined' && 29 | typeof a.forward !== 'undefined'); 30 | }; 31 | 32 | L.Proj.Projection = L.Class.extend({ 33 | initialize: function(code, def, bounds) { 34 | var isP4 = L.Proj._isProj4Obj(code); 35 | this._proj = isP4 ? code : this._projFromCodeDef(code, def); 36 | this.bounds = isP4 ? def : bounds; 37 | }, 38 | 39 | project: function (latlng) { 40 | var point = this._proj.forward([latlng.lng, latlng.lat]); 41 | return new L.Point(point[0], point[1]); 42 | }, 43 | 44 | unproject: function (point, unbounded) { 45 | var point2 = this._proj.inverse([point.x, point.y]); 46 | return new L.LatLng(point2[1], point2[0], unbounded); 47 | }, 48 | 49 | _projFromCodeDef: function(code, def) { 50 | if (def) { 51 | proj4.defs(code, def); 52 | } else if (proj4.defs[code] === undefined) { 53 | var urn = code.split(':'); 54 | if (urn.length > 3) { 55 | code = urn[urn.length - 3] + ':' + urn[urn.length - 1]; 56 | } 57 | if (proj4.defs[code] === undefined) { 58 | throw 'No projection definition for code ' + code; 59 | } 60 | } 61 | 62 | return proj4(code); 63 | } 64 | }); 65 | 66 | L.Proj.CRS = L.Class.extend({ 67 | includes: L.CRS, 68 | 69 | options: { 70 | transformation: new L.Transformation(1, 0, -1, 0) 71 | }, 72 | 73 | initialize: function(a, b, c) { 74 | var code, 75 | proj, 76 | def, 77 | options; 78 | 79 | if (L.Proj._isProj4Obj(a)) { 80 | proj = a; 81 | code = proj.srsCode; 82 | options = b || {}; 83 | 84 | this.projection = new L.Proj.Projection(proj, options.bounds); 85 | } else { 86 | code = a; 87 | def = b; 88 | options = c || {}; 89 | this.projection = new L.Proj.Projection(code, def, options.bounds); 90 | } 91 | 92 | L.Util.setOptions(this, options); 93 | this.code = code; 94 | this.transformation = this.options.transformation; 95 | 96 | if (this.options.origin) { 97 | this.transformation = 98 | new L.Transformation(1, -this.options.origin[0], 99 | -1, this.options.origin[1]); 100 | } 101 | 102 | if (this.options.scales) { 103 | this._scales = this.options.scales; 104 | } else if (this.options.resolutions) { 105 | this._scales = []; 106 | for (var i = this.options.resolutions.length - 1; i >= 0; i--) { 107 | if (this.options.resolutions[i]) { 108 | this._scales[i] = 1 / this.options.resolutions[i]; 109 | } 110 | } 111 | } 112 | 113 | this.infinite = !this.options.bounds; 114 | 115 | }, 116 | 117 | scale: function(zoom) { 118 | var iZoom = Math.floor(zoom), 119 | baseScale, 120 | nextScale, 121 | scaleDiff, 122 | zDiff; 123 | if (zoom === iZoom) { 124 | return this._scales[zoom]; 125 | } else { 126 | // Non-integer zoom, interpolate 127 | baseScale = this._scales[iZoom]; 128 | nextScale = this._scales[iZoom + 1]; 129 | scaleDiff = nextScale - baseScale; 130 | zDiff = (zoom - iZoom); 131 | return baseScale + scaleDiff * zDiff; 132 | } 133 | }, 134 | 135 | zoom: function(scale) { 136 | // Find closest number in this._scales, down 137 | var downScale = this._closestElement(this._scales, scale), 138 | downZoom = this._scales.indexOf(downScale), 139 | nextScale, 140 | nextZoom, 141 | scaleDiff; 142 | // Check if scale is downScale => return array index 143 | if (scale === downScale) { 144 | return downZoom; 145 | } 146 | if (downScale === undefined) { 147 | return -Infinity; 148 | } 149 | // Interpolate 150 | nextZoom = downZoom + 1; 151 | nextScale = this._scales[nextZoom]; 152 | if (nextScale === undefined) { 153 | return Infinity; 154 | } 155 | scaleDiff = nextScale - downScale; 156 | return (scale - downScale) / scaleDiff + downZoom; 157 | }, 158 | 159 | distance: L.CRS.Earth.distance, 160 | 161 | R: L.CRS.Earth.R, 162 | 163 | /* Get the closest lowest element in an array */ 164 | _closestElement: function(array, element) { 165 | var low; 166 | for (var i = array.length; i--;) { 167 | if (array[i] <= element && (low === undefined || low < array[i])) { 168 | low = array[i]; 169 | } 170 | } 171 | return low; 172 | } 173 | }); 174 | 175 | L.Proj.GeoJSON = L.GeoJSON.extend({ 176 | initialize: function(geojson, options) { 177 | this._callLevel = 0; 178 | L.GeoJSON.prototype.initialize.call(this, geojson, options); 179 | }, 180 | 181 | addData: function(geojson) { 182 | var crs; 183 | 184 | if (geojson) { 185 | if (geojson.crs && geojson.crs.type === 'name') { 186 | crs = new L.Proj.CRS(geojson.crs.properties.name); 187 | } else if (geojson.crs && geojson.crs.type) { 188 | crs = new L.Proj.CRS(geojson.crs.type + ':' + geojson.crs.properties.code); 189 | } 190 | 191 | if (crs !== undefined) { 192 | this.options.coordsToLatLng = function(coords) { 193 | var point = L.point(coords[0], coords[1]); 194 | return crs.projection.unproject(point); 195 | }; 196 | } 197 | } 198 | 199 | // Base class' addData might call us recursively, but 200 | // CRS shouldn't be cleared in that case, since CRS applies 201 | // to the whole GeoJSON, inluding sub-features. 202 | this._callLevel++; 203 | try { 204 | L.GeoJSON.prototype.addData.call(this, geojson); 205 | } finally { 206 | this._callLevel--; 207 | if (this._callLevel === 0) { 208 | delete this.options.coordsToLatLng; 209 | } 210 | } 211 | } 212 | }); 213 | 214 | L.Proj.geoJson = function(geojson, options) { 215 | return new L.Proj.GeoJSON(geojson, options); 216 | }; 217 | 218 | L.Proj.ImageOverlay = L.ImageOverlay.extend({ 219 | initialize: function (url, bounds, options) { 220 | L.ImageOverlay.prototype.initialize.call(this, url, null, options); 221 | this._projectedBounds = bounds; 222 | }, 223 | 224 | // Danger ahead: Overriding internal methods in Leaflet. 225 | // Decided to do this rather than making a copy of L.ImageOverlay 226 | // and doing very tiny modifications to it. 227 | // Future will tell if this was wise or not. 228 | _animateZoom: function (event) { 229 | var scale = this._map.getZoomScale(event.zoom); 230 | var northWest = L.point(this._projectedBounds.min.x, this._projectedBounds.max.y); 231 | var offset = this._projectedToNewLayerPoint(northWest, event.zoom, event.center); 232 | 233 | L.DomUtil.setTransform(this._image, offset, scale); 234 | }, 235 | 236 | _reset: function () { 237 | var zoom = this._map.getZoom(); 238 | var pixelOrigin = this._map.getPixelOrigin(); 239 | var bounds = L.bounds( 240 | this._transform(this._projectedBounds.min, zoom)._subtract(pixelOrigin), 241 | this._transform(this._projectedBounds.max, zoom)._subtract(pixelOrigin) 242 | ); 243 | var size = bounds.getSize(); 244 | 245 | L.DomUtil.setPosition(this._image, bounds.min); 246 | this._image.style.width = size.x + 'px'; 247 | this._image.style.height = size.y + 'px'; 248 | }, 249 | 250 | _projectedToNewLayerPoint: function (point, zoom, center) { 251 | var viewHalf = this._map.getSize()._divideBy(2); 252 | var newTopLeft = this._map.project(center, zoom)._subtract(viewHalf)._round(); 253 | var topLeft = newTopLeft.add(this._map._getMapPanePos()); 254 | 255 | return this._transform(point, zoom)._subtract(topLeft); 256 | }, 257 | 258 | _transform: function (point, zoom) { 259 | var crs = this._map.options.crs; 260 | var transformation = crs.transformation; 261 | var scale = crs.scale(zoom); 262 | 263 | return transformation.transform(point, scale); 264 | } 265 | }); 266 | 267 | L.Proj.imageOverlay = function (url, bounds, options) { 268 | return new L.Proj.ImageOverlay(url, bounds, options); 269 | }; 270 | 271 | return L.Proj; 272 | })); 273 | -------------------------------------------------------------------------------- /libs/crosstalk/css/crosstalk.css: -------------------------------------------------------------------------------- 1 | /* Adjust margins outwards, so column contents line up with the edges of the 2 | parent of container-fluid. */ 3 | .container-fluid.crosstalk-bscols { 4 | margin-left: -30px; 5 | margin-right: -30px; 6 | white-space: normal; 7 | } 8 | 9 | /* But don't adjust the margins outwards if we're directly under the body, 10 | i.e. we were the top-level of something at the console. */ 11 | body > .container-fluid.crosstalk-bscols { 12 | margin-left: auto; 13 | margin-right: auto; 14 | } 15 | 16 | .crosstalk-input-checkboxgroup .crosstalk-options-group .crosstalk-options-column { 17 | display: inline-block; 18 | padding-right: 12px; 19 | vertical-align: top; 20 | } 21 | 22 | @media only screen and (max-width:480px) { 23 | .crosstalk-input-checkboxgroup .crosstalk-options-group .crosstalk-options-column { 24 | display: block; 25 | padding-right: inherit; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /libs/crosstalk/js/crosstalk.min.js: -------------------------------------------------------------------------------- 1 | !function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;gb?1:void 0}Object.defineProperty(c,"__esModule",{value:!0});var f=function(){function a(a,b){for(var c=0;c0&&void 0!==arguments[0]?arguments[0]:this._allKeys,b=Object.keys(this._handles).length;if(0===b)this._value=null;else{this._value=[];for(var c=0;c?@\[\\\]^`{|}~])/g,"\\$1")}function f(a){var b=h(a);Object.keys(i).forEach(function(c){if(b.hasClass(c)&&!b.hasClass("crosstalk-input-bound")){var d=i[c];g(d,a)}})}function g(a,b){var c=h(b).find("script[type='application/json'][data-for='"+e(b.id)+"']"),d=JSON.parse(c[0].innerText),f=a.factory(b,d);h(b).data("crosstalk-instance",f),h(b).addClass("crosstalk-input-bound")}Object.defineProperty(c,"__esModule",{value:!0}),c.register=b;var h=a.jQuery,i={};a.Shiny&&!function(){var b=new a.Shiny.InputBinding,c=a.jQuery;c.extend(b,{find:function(a){return c(a).find(".crosstalk-input")},initialize:function(a){c(a).hasClass("crosstalk-input-bound")||f(a)},getId:function(a){return a.id},getValue:function(a){},setValue:function(a,b){},receiveMessage:function(a,b){},subscribe:function(a,b){c(a).data("crosstalk-instance").resume()},unsubscribe:function(a){c(a).data("crosstalk-instance").suspend()}}),a.Shiny.inputBindings.register(b,"crosstalk.inputBinding")}()}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],7:[function(a,b,c){(function(b){"use strict";function c(a){if(a&&a.__esModule)return a;var b={};if(null!=a)for(var c in a)Object.prototype.hasOwnProperty.call(a,c)&&(b[c]=a[c]);return b.default=a,b}var d=a("./input"),e=c(d),f=a("./filter"),g=b.jQuery;e.register({className:"crosstalk-input-checkboxgroup",factory:function(a,b){var c=new f.FilterHandle(b.group),d=void 0,e=g(a);return e.on("change","input[type='checkbox']",function(){var a=e.find("input[type='checkbox']:checked");0===a.length?(d=null,c.clear()):!function(){var e={};a.each(function(){b.map[this.value].forEach(function(a){e[a]=!0})});var f=Object.keys(e);f.sort(),d=f,c.set(f)}()}),{suspend:function(){c.clear()},resume:function(){d&&c.set(d)}}}})}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./filter":2,"./input":6}],8:[function(a,b,c){(function(b){"use strict";function c(a){if(a&&a.__esModule)return a;var b={};if(null!=a)for(var c in a)Object.prototype.hasOwnProperty.call(a,c)&&(b[c]=a[c]);return b.default=a,b}var d=a("./input"),e=c(d),f=a("./util"),g=c(f),h=a("./filter"),i=b.jQuery;e.register({className:"crosstalk-input-select",factory:function(a,b){var c=[{value:"",label:"(All)"}],d=g.dataframeToD3(b.items),e={options:c.concat(d),valueField:"value",labelField:"label",searchField:"label"},f=i(a).find("select")[0],j=i(f).selectize(e)[0].selectize,k=new h.FilterHandle(b.group),l=void 0;return j.on("change",function(){0===j.items.length?(l=null,k.clear()):!function(){var a={};j.items.forEach(function(c){b.map[c].forEach(function(b){a[b]=!0})});var c=Object.keys(a);c.sort(),l=c,k.set(c)}()}),{suspend:function(){k.clear()},resume:function(){l&&k.set(l)}}}})}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./filter":2,"./input":6,"./util":11}],9:[function(a,b,c){(function(b){"use strict";function c(a){if(a&&a.__esModule)return a;var b={};if(null!=a)for(var c in a)Object.prototype.hasOwnProperty.call(a,c)&&(b[c]=a[c]);return b.default=a,b}function d(a,b){for(var c=a.toString();c.length=i&&m<=j&&k.push(b.keys[l])}k.sort(),d.set(k),p=k}}),{suspend:function(){d.clear()},resume:function(){p&&d.set(p)}}}})}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./filter":2,"./input":6}],10:[function(a,b,c){"use strict";function d(a){if(a&&a.__esModule)return a;var b={};if(null!=a)for(var c in a)Object.prototype.hasOwnProperty.call(a,c)&&(b[c]=a[c]);return b.default=a,b}function e(a){return a&&a.__esModule?a:{default:a}}function f(a,b){if(!(a instanceof b))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(c,"__esModule",{value:!0}),c.SelectionHandle=void 0;var g=function(){function a(a,b){for(var c=0;c0&&void 0!==arguments[0]?arguments[0]:null,c=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;f(this,a),this._eventRelay=new i.default,this._emitter=new m.SubscriptionTracker(this._eventRelay),this._group=null,this._var=null,this._varOnChangeSub=null,this._extraInfo=m.extend({sender:this},c),this.setGroup(b)}return g(a,[{key:"setGroup",value:function(a){var b=this;if(this._group!==a&&(this._group||a)&&(this._var&&(this._var.off("change",this._varOnChangeSub),this._var=null,this._varOnChangeSub=null),this._group=a,a)){this._var=(0,k.default)(a).var("selection");var c=this._var.on("change",function(a){b._eventRelay.trigger("change",a,b)});this._varOnChangeSub=c}}},{key:"_mergeExtraInfo",value:function(a){return m.extend({},this._extraInfo?this._extraInfo:null,a?a:null)}},{key:"set",value:function(a,b){this._var&&this._var.set(a,this._mergeExtraInfo(b))}},{key:"clear",value:function(a){this._var&&this.set(void 0,this._mergeExtraInfo(a))}},{key:"on",value:function(a,b){return this._emitter.on(a,b)}},{key:"off",value:function(a,b){return this._emitter.off(a,b)}},{key:"close",value:function(){this._emitter.removeAllListeners(),this.setGroup(null)}},{key:"value",get:function(){return this._var?this._var.get():null}}]),a}()},{"./events":1,"./group":4,"./util":11}],11:[function(a,b,c){"use strict";function d(a,b){if(!(a instanceof b))throw new TypeError("Cannot call a class as a function")}function e(a){for(var b=arguments.length,c=Array(b>1?b-1:0),d=1;d {\n this._eventRelay.trigger(\"change\", e, this);\n });\n this._varOnChangeSub = sub;\n }\n }\n\n /**\n * Combine the given `extraInfo` (if any) with the handle's default\n * `_extraInfo` (if any).\n * @private\n */\n _mergeExtraInfo(extraInfo) {\n return util.extend({},\n this._extraInfo ? this._extraInfo : null,\n extraInfo ? extraInfo : null);\n }\n\n /**\n * Close the handle. This clears this handle's contribution to the filter set,\n * and unsubscribes all event listeners.\n */\n close() {\n this._emitter.removeAllListeners();\n this.clear();\n this.setGroup(null);\n }\n\n /**\n * Clear this handle's contribution to the filter set.\n *\n * @param {Object} [extraInfo] - Extra properties to be included on the event\n * object that's passed to listeners (in addition to any options that were\n * passed into the `FilterHandle` constructor).\n */\n clear(extraInfo) {\n if (!this._filterSet)\n return;\n this._filterSet.clear(this._id);\n this._onChange(extraInfo);\n }\n\n /**\n * Set this handle's contribution to the filter set. This array should consist\n * of the keys of the rows that _should_ be displayed; any keys that are not\n * present in the array will be considered _filtered out_. Note that multiple\n * `FilterHandle` instances in the group may each contribute an array of keys,\n * and only those keys that appear in _all_ of the arrays make it through the\n * filter.\n *\n * @param {string[]} keys - Empty array, or array of keys. To clear the\n * filter, don't pass an empty array; instead, use the\n * {@link FilterHandle#clear} method.\n * @param {Object} [extraInfo] - Extra properties to be included on the event\n * object that's passed to listeners (in addition to any options that were\n * passed into the `FilterHandle` constructor).\n */\n set(keys, extraInfo) {\n if (!this._filterSet)\n return;\n this._filterSet.update(this._id, keys);\n this._onChange(extraInfo);\n }\n\n /**\n * @return {string[]|null} - Either: 1) an array of keys that made it through\n * all of the `FilterHandle` instances, or, 2) `null`, which means no filter\n * is being applied (all data should be displayed).\n */\n get filteredKeys() {\n return this._filterSet ? this._filterSet.value : null;\n }\n\n /**\n * Subscribe to events on this `FilterHandle`.\n *\n * @param {string} eventType - Indicates the type of events to listen to.\n * Currently, only `\"change\"` is supported.\n * @param {FilterHandle~listener} listener - The callback function that\n * will be invoked when the event occurs.\n * @return {string} - A token to pass to {@link FilterHandle#off} to cancel\n * this subscription.\n */\n on(eventType, listener) {\n return this._emitter.on(eventType, listener);\n }\n\n /**\n * Cancel event subscriptions created by {@link FilterHandle#on}.\n *\n * @param {string} eventType - The type of event to unsubscribe.\n * @param {string|FilterHandle~listener} listener - Either the callback\n * function previously passed into {@link FilterHandle#on}, or the\n * string that was returned from {@link FilterHandle#on}.\n */\n off(eventType, listener) {\n return this._emitter.off(eventType, listener);\n }\n\n _onChange(extraInfo) {\n if (!this._filterSet)\n return;\n this._filterVar.set(this._filterSet.value, this._mergeExtraInfo(extraInfo));\n }\n\n /**\n * @callback FilterHandle~listener\n * @param {Object} event - An object containing details of the event. For\n * `\"change\"` events, this includes the properties `value` (the new\n * value of the filter set, or `null` if no filter set is active),\n * `oldValue` (the previous value of the filter set), and `sender` (the\n * `FilterHandle` instance that made the change).\n */\n\n /**\n * @event FilterHandle#change\n * @type {object}\n * @property {object} value - The new value of the filter set, or `null`\n * if no filter set is active.\n * @property {object} oldValue - The previous value of the filter set.\n * @property {FilterHandle} sender - The `FilterHandle` instance that\n * changed the value.\n */\n}\n","import { diffSortedLists } from \"./util\";\n\nfunction naturalComparator(a, b) {\n if (a === b) {\n return 0;\n } else if (a < b) {\n return -1;\n } else if (a > b) {\n return 1;\n }\n}\n\n/**\n * @private\n */\nexport default class FilterSet {\n constructor() {\n this.reset();\n }\n\n reset() {\n // Key: handle ID, Value: array of selected keys, or null\n this._handles = {};\n // Key: key string, Value: count of handles that include it\n this._keys = {};\n this._value = null;\n this._activeHandles = 0;\n }\n\n get value() {\n return this._value;\n }\n\n update(handleId, keys) {\n if (keys !== null) {\n keys = keys.slice(0); // clone before sorting\n keys.sort(naturalComparator);\n }\n\n let {added, removed} = diffSortedLists(this._handles[handleId], keys);\n this._handles[handleId] = keys;\n\n for (let i = 0; i < added.length; i++) {\n this._keys[added[i]] = (this._keys[added[i]] || 0) + 1;\n }\n for (let i = 0; i < removed.length; i++) {\n this._keys[removed[i]]--;\n }\n\n this._updateValue(keys);\n }\n\n /**\n * @param {string[]} keys Sorted array of strings that indicate\n * a superset of possible keys.\n * @private\n */\n _updateValue(keys = this._allKeys) {\n let handleCount = Object.keys(this._handles).length;\n if (handleCount === 0) {\n this._value = null;\n } else {\n this._value = [];\n for (let i = 0; i < keys.length; i++) {\n let count = this._keys[keys[i]];\n if (count === handleCount) {\n this._value.push(keys[i]);\n }\n }\n }\n }\n\n clear(handleId) {\n if (typeof(this._handles[handleId]) === \"undefined\") {\n return;\n }\n\n let keys = this._handles[handleId];\n if (!keys) {\n keys = [];\n }\n\n for (let i = 0; i < keys.length; i++) {\n this._keys[keys[i]]--;\n }\n delete this._handles[handleId];\n\n this._updateValue();\n }\n\n get _allKeys() {\n let allKeys = Object.keys(this._keys);\n allKeys.sort(naturalComparator);\n return allKeys;\n }\n}\n","import Var from \"./var\";\n\n// Use a global so that multiple copies of crosstalk.js can be loaded and still\n// have groups behave as singletons across all copies.\nglobal.__crosstalk_groups = global.__crosstalk_groups || {};\nlet groups = global.__crosstalk_groups;\n\nexport default function group(groupName) {\n if (groupName && typeof(groupName) === \"string\") {\n if (!groups.hasOwnProperty(groupName)) {\n groups[groupName] = new Group(groupName);\n }\n return groups[groupName];\n } else if (typeof(groupName) === \"object\" && groupName._vars && groupName.var) {\n // Appears to already be a group object\n return groupName;\n } else if (Array.isArray(groupName) &&\n groupName.length == 1 &&\n typeof(groupName[0]) === \"string\") {\n return group(groupName[0]);\n } else {\n throw new Error(\"Invalid groupName argument\");\n }\n}\n\nclass Group {\n constructor(name) {\n this.name = name;\n this._vars = {};\n }\n\n var(name) {\n if (!name || typeof(name) !== \"string\") {\n throw new Error(\"Invalid var name\");\n }\n\n if (!this._vars.hasOwnProperty(name))\n this._vars[name] = new Var(this, name);\n return this._vars[name];\n }\n\n has(name) {\n if (!name || typeof(name) !== \"string\") {\n throw new Error(\"Invalid var name\");\n }\n\n return this._vars.hasOwnProperty(name);\n }\n}\n","import group from \"./group\";\nimport { SelectionHandle } from \"./selection\";\nimport { FilterHandle } from \"./filter\";\nimport \"./input\";\nimport \"./input_selectize\";\nimport \"./input_checkboxgroup\";\nimport \"./input_slider\";\n\nconst defaultGroup = group(\"default\");\n\nfunction var_(name) {\n return defaultGroup.var(name);\n}\n\nfunction has(name) {\n return defaultGroup.has(name);\n}\n\nif (global.Shiny) {\n global.Shiny.addCustomMessageHandler(\"update-client-value\", function(message) {\n if (typeof(message.group) === \"string\") {\n group(message.group).var(message.name).set(message.value);\n } else {\n var_(message.name).set(message.value);\n }\n });\n}\n\nconst crosstalk = {\n group: group,\n var: var_,\n has: has,\n SelectionHandle: SelectionHandle,\n FilterHandle: FilterHandle\n};\n\n/**\n * @namespace crosstalk\n */\nexport default crosstalk;\nglobal.crosstalk = crosstalk;\n","let $ = global.jQuery;\n\nlet bindings = {};\n\nexport function register(reg) {\n bindings[reg.className] = reg;\n if (global.document && global.document.readyState !== \"complete\") {\n $(() => {\n bind();\n });\n } else if (global.document) {\n setTimeout(bind, 100);\n }\n}\n\nfunction bind() {\n Object.keys(bindings).forEach(function(className) {\n let binding = bindings[className];\n $(\".\" + binding.className).not(\".crosstalk-input-bound\").each(function(i, el) {\n bindInstance(binding, el);\n });\n });\n}\n\n// Escape jQuery identifier\nfunction $escape(val) {\n return val.replace(/([!\"#$%&'()*+,.\\/:;<=>?@\\[\\\\\\]^`{|}~])/g, \"\\\\$1\");\n}\n\nfunction bindEl(el) {\n let $el = $(el);\n Object.keys(bindings).forEach(function(className) {\n if ($el.hasClass(className) && !$el.hasClass(\"crosstalk-input-bound\")) {\n let binding = bindings[className];\n bindInstance(binding, el);\n }\n });\n}\n\nfunction bindInstance(binding, el) {\n let jsonEl = $(el).find(\"script[type='application/json'][data-for='\" + $escape(el.id) + \"']\");\n let data = JSON.parse(jsonEl[0].innerText);\n\n let instance = binding.factory(el, data);\n $(el).data(\"crosstalk-instance\", instance);\n $(el).addClass(\"crosstalk-input-bound\");\n}\n\nif (global.Shiny) {\n let inputBinding = new global.Shiny.InputBinding();\n let $ = global.jQuery;\n $.extend(inputBinding, {\n find: function(scope) {\n return $(scope).find(\".crosstalk-input\");\n },\n initialize: function(el) {\n if (!$(el).hasClass(\"crosstalk-input-bound\")) {\n bindEl(el);\n }\n },\n getId: function(el) {\n return el.id;\n },\n getValue: function(el) {\n\n },\n setValue: function(el, value) {\n\n },\n receiveMessage: function(el, data) {\n\n },\n subscribe: function(el, callback) {\n $(el).data(\"crosstalk-instance\").resume();\n },\n unsubscribe: function(el) {\n $(el).data(\"crosstalk-instance\").suspend();\n }\n });\n global.Shiny.inputBindings.register(inputBinding, \"crosstalk.inputBinding\");\n}\n","import * as input from \"./input\";\nimport { FilterHandle } from \"./filter\";\n\nlet $ = global.jQuery;\n\ninput.register({\n className: \"crosstalk-input-checkboxgroup\",\n\n factory: function(el, data) {\n /*\n * map: {\"groupA\": [\"keyA\", \"keyB\", ...], ...}\n * group: \"ct-groupname\"\n */\n let ctHandle = new FilterHandle(data.group);\n\n let lastKnownKeys;\n let $el = $(el);\n $el.on(\"change\", \"input[type='checkbox']\", function() {\n let checked = $el.find(\"input[type='checkbox']:checked\");\n if (checked.length === 0) {\n lastKnownKeys = null;\n ctHandle.clear();\n } else {\n let keys = {};\n checked.each(function() {\n data.map[this.value].forEach(function(key) {\n keys[key] = true;\n });\n });\n let keyArray = Object.keys(keys);\n keyArray.sort();\n lastKnownKeys = keyArray;\n ctHandle.set(keyArray);\n }\n });\n\n return {\n suspend: function() {\n ctHandle.clear();\n },\n resume: function() {\n if (lastKnownKeys)\n ctHandle.set(lastKnownKeys);\n }\n };\n }\n});\n","import * as input from \"./input\";\nimport * as util from \"./util\";\nimport { FilterHandle } from \"./filter\";\n\nlet $ = global.jQuery;\n\ninput.register({\n className: \"crosstalk-input-select\",\n\n factory: function(el, data) {\n /*\n * items: {value: [...], label: [...]}\n * map: {\"groupA\": [\"keyA\", \"keyB\", ...], ...}\n * group: \"ct-groupname\"\n */\n\n let first = [{value: \"\", label: \"(All)\"}];\n let items = util.dataframeToD3(data.items);\n let opts = {\n options: first.concat(items),\n valueField: \"value\",\n labelField: \"label\",\n searchField: \"label\"\n };\n\n let select = $(el).find(\"select\")[0];\n\n let selectize = $(select).selectize(opts)[0].selectize;\n\n let ctHandle = new FilterHandle(data.group);\n\n let lastKnownKeys;\n selectize.on(\"change\", function() {\n if (selectize.items.length === 0) {\n lastKnownKeys = null;\n ctHandle.clear();\n } else {\n let keys = {};\n selectize.items.forEach(function(group) {\n data.map[group].forEach(function(key) {\n keys[key] = true;\n });\n });\n let keyArray = Object.keys(keys);\n keyArray.sort();\n lastKnownKeys = keyArray;\n ctHandle.set(keyArray);\n }\n });\n\n return {\n suspend: function() {\n ctHandle.clear();\n },\n resume: function() {\n if (lastKnownKeys)\n ctHandle.set(lastKnownKeys);\n }\n };\n }\n});\n","import * as input from \"./input\";\nimport { FilterHandle } from \"./filter\";\n\nlet $ = global.jQuery;\nlet strftime = global.strftime;\n\ninput.register({\n className: \"crosstalk-input-slider\",\n\n factory: function(el, data) {\n /*\n * map: {\"groupA\": [\"keyA\", \"keyB\", ...], ...}\n * group: \"ct-groupname\"\n */\n let ctHandle = new FilterHandle(data.group);\n\n let opts = {};\n let $el = $(el).find(\"input\");\n let dataType = $el.data(\"data-type\");\n let timeFormat = $el.data(\"time-format\");\n let timeFormatter;\n\n // Set up formatting functions\n if (dataType === \"date\") {\n timeFormatter = strftime.utc();\n opts.prettify = function(num) {\n return timeFormatter(timeFormat, new Date(num));\n };\n\n } else if (dataType === \"datetime\") {\n let timezone = $el.data(\"timezone\");\n if (timezone)\n timeFormatter = strftime.timezone(timezone);\n else\n timeFormatter = strftime;\n\n opts.prettify = function(num) {\n return timeFormatter(timeFormat, new Date(num));\n };\n }\n\n $el.ionRangeSlider(opts);\n\n function getValue() {\n let result = $el.data(\"ionRangeSlider\").result;\n\n // Function for converting numeric value from slider to appropriate type.\n let convert;\n let dataType = $el.data(\"data-type\");\n if (dataType === \"date\") {\n convert = function(val) {\n return formatDateUTC(new Date(+val));\n };\n } else if (dataType === \"datetime\") {\n convert = function(val) {\n // Convert ms to s\n return +val / 1000;\n };\n } else {\n convert = function(val) { return +val; };\n }\n\n if ($el.data(\"ionRangeSlider\").options.type === \"double\") {\n return [convert(result.from), convert(result.to)];\n } else {\n return convert(result.from);\n }\n }\n\n let lastKnownKeys = null;\n\n $el.on(\"change.crosstalkSliderInput\", function(event) {\n if (!$el.data(\"updating\") && !$el.data(\"animating\")) {\n let [from, to] = getValue();\n let keys = [];\n for (let i = 0; i < data.values.length; i++) {\n let val = data.values[i];\n if (val >= from && val <= to) {\n keys.push(data.keys[i]);\n }\n }\n keys.sort();\n ctHandle.set(keys);\n lastKnownKeys = keys;\n }\n });\n\n\n // let $el = $(el);\n // $el.on(\"change\", \"input[type=\"checkbox\"]\", function() {\n // let checked = $el.find(\"input[type=\"checkbox\"]:checked\");\n // if (checked.length === 0) {\n // ctHandle.clear();\n // } else {\n // let keys = {};\n // checked.each(function() {\n // data.map[this.value].forEach(function(key) {\n // keys[key] = true;\n // });\n // });\n // let keyArray = Object.keys(keys);\n // keyArray.sort();\n // ctHandle.set(keyArray);\n // }\n // });\n\n return {\n suspend: function() {\n ctHandle.clear();\n },\n resume: function() {\n if (lastKnownKeys)\n ctHandle.set(lastKnownKeys);\n }\n };\n }\n});\n\n\n// Convert a number to a string with leading zeros\nfunction padZeros(n, digits) {\n let str = n.toString();\n while (str.length < digits)\n str = \"0\" + str;\n return str;\n}\n\n// Given a Date object, return a string in yyyy-mm-dd format, using the\n// UTC date. This may be a day off from the date in the local time zone.\nfunction formatDateUTC(date) {\n if (date instanceof Date) {\n return date.getUTCFullYear() + \"-\" +\n padZeros(date.getUTCMonth()+1, 2) + \"-\" +\n padZeros(date.getUTCDate(), 2);\n\n } else {\n return null;\n }\n}\n","import Events from \"./events\";\nimport grp from \"./group\";\nimport * as util from \"./util\";\n\nexport class SelectionHandle {\n\n /**\n * @classdesc\n * Use this class to read and write (and listen for changes to) the selection\n * for a Crosstalk group. This is intended to be used for linked brushing.\n *\n * If two (or more) `SelectionHandle` instances in the same webpage share the\n * same group name, they will share the same state. Setting the selection using\n * one `SelectionHandle` instance will result in the `value` property instantly\n * changing across the others, and `\"change\"` event listeners on all instances\n * (including the one that initiated the sending) will fire.\n *\n * @param {string} [group] - The name of the Crosstalk group, or if none,\n * null or undefined (or any other falsy value). This can be changed later\n * via the [SelectionHandle#setGroup](#setGroup) method.\n * @param {Object} [extraInfo] - An object whose properties will be copied to\n * the event object whenever an event is emitted.\n */\n constructor(group = null, extraInfo = null) {\n this._eventRelay = new Events();\n this._emitter = new util.SubscriptionTracker(this._eventRelay);\n\n // Name of the group we're currently tracking, if any. Can change over time.\n this._group = null;\n // The Var we're currently tracking, if any. Can change over time.\n this._var = null;\n // The event handler subscription we currently have on var.on(\"change\").\n this._varOnChangeSub = null;\n\n this._extraInfo = util.extend({ sender: this }, extraInfo);\n\n this.setGroup(group);\n }\n\n /**\n * Changes the Crosstalk group membership of this SelectionHandle. The group\n * being switched away from (if any) will not have its selection value\n * modified as a result of calling `setGroup`, even if this handle was the\n * most recent handle to set the selection of the group.\n *\n * The group being switched to (if any) will also not have its selection value\n * modified as a result of calling `setGroup`. If you want to set the\n * selection value of the new group, call `set` explicitly.\n *\n * @param {string} group - The name of the Crosstalk group, or null (or\n * undefined) to clear the group.\n */\n setGroup(group) {\n // If group is unchanged, do nothing\n if (this._group === group)\n return;\n // Treat null, undefined, and other falsy values the same\n if (!this._group && !group)\n return;\n\n if (this._var) {\n this._var.off(\"change\", this._varOnChangeSub);\n this._var = null;\n this._varOnChangeSub = null;\n }\n\n this._group = group;\n\n if (group) {\n this._var = grp(group).var(\"selection\");\n let sub = this._var.on(\"change\", (e) => {\n this._eventRelay.trigger(\"change\", e, this);\n });\n this._varOnChangeSub = sub;\n }\n }\n\n /**\n * Retrieves the current selection for the group represented by this\n * `SelectionHandle`.\n *\n * - If no selection is active, then this value will be falsy.\n * - If a selection is active, but no data points are selected, then this\n * value will be an empty array.\n * - If a selection is active, and data points are selected, then the keys\n * of the selected data points will be present in the array.\n */\n get value() {\n return this._var ? this._var.get() : null;\n }\n\n /**\n * Combines the given `extraInfo` (if any) with the handle's default\n * `_extraInfo` (if any).\n * @private\n */\n _mergeExtraInfo(extraInfo) {\n // Important incidental effect: shallow clone is returned\n return util.extend({},\n this._extraInfo ? this._extraInfo : null,\n extraInfo ? extraInfo : null);\n }\n\n /**\n * Overwrites the current selection for the group, and raises the `\"change\"`\n * event among all of the group's '`SelectionHandle` instances (including\n * this one).\n *\n * @fires SelectionHandle#change\n * @param {string[]} selectedKeys - Falsy, empty array, or array of keys (see\n * {@link SelectionHandle#value}).\n * @param {Object} [extraInfo] - Extra properties to be included on the event\n * object that's passed to listeners (in addition to any options that were\n * passed into the `SelectionHandle` constructor).\n */\n set(selectedKeys, extraInfo) {\n if (this._var)\n this._var.set(selectedKeys, this._mergeExtraInfo(extraInfo));\n }\n\n /**\n * Overwrites the current selection for the group, and raises the `\"change\"`\n * event among all of the group's '`SelectionHandle` instances (including\n * this one).\n *\n * @fires SelectionHandle#change\n * @param {Object} [extraInfo] - Extra properties to be included on the event\n * object that's passed to listeners (in addition to any that were passed\n * into the `SelectionHandle` constructor).\n */\n clear(extraInfo) {\n if (this._var)\n this.set(void 0, this._mergeExtraInfo(extraInfo));\n }\n\n /**\n * Subscribes to events on this `SelectionHandle`.\n *\n * @param {string} eventType - Indicates the type of events to listen to.\n * Currently, only `\"change\"` is supported.\n * @param {SelectionHandle~listener} listener - The callback function that\n * will be invoked when the event occurs.\n * @return {string} - A token to pass to {@link SelectionHandle#off} to cancel\n * this subscription.\n */\n on(eventType, listener) {\n return this._emitter.on(eventType, listener);\n }\n\n /**\n * Cancels event subscriptions created by {@link SelectionHandle#on}.\n *\n * @param {string} eventType - The type of event to unsubscribe.\n * @param {string|SelectionHandle~listener} listener - Either the callback\n * function previously passed into {@link SelectionHandle#on}, or the\n * string that was returned from {@link SelectionHandle#on}.\n */\n off(eventType, listener) {\n return this._emitter.off(eventType, listener);\n }\n\n /**\n * Shuts down the `SelectionHandle` object.\n *\n * Removes all event listeners that were added through this handle.\n */\n close() {\n this._emitter.removeAllListeners();\n this.setGroup(null);\n }\n\n /**\n * @callback SelectionHandle~listener\n * @param {Object} event - An object containing details of the event. For\n * `\"change\"` events, this includes the properties `value` (the new\n * value of the selection, or `undefined` if no selection is active),\n * `oldValue` (the previous value of the selection), and `sender` (the\n * `SelectionHandle` instance that made the change).\n */\n\n /**\n * @event SelectionHandle#change\n * @type {object}\n * @property {object} value - The new value of the selection, or `undefined`\n * if no selection is active.\n * @property {object} oldValue - The previous value of the selection.\n * @property {SelectionHandle} sender - The `SelectionHandle` instance that\n * changed the value.\n */\n}\n","export function extend(target, ...sources) {\n for (let i = 0; i < sources.length; i++) {\n let src = sources[i];\n if (typeof(src) === \"undefined\" || src === null)\n continue;\n\n for (let key in src) {\n if (src.hasOwnProperty(key)) {\n target[key] = src[key];\n }\n }\n }\n return target;\n}\n\nexport function checkSorted(list) {\n for (let i = 1; i < list.length; i++) {\n if (list[i] <= list[i-1]) {\n throw new Error(\"List is not sorted or contains duplicate\");\n }\n }\n}\n\nexport function diffSortedLists(a, b) {\n let i_a = 0;\n let i_b = 0;\n\n if (!a) a = [];\n if (!b) b = [];\n\n let a_only = [];\n let b_only = [];\n\n checkSorted(a);\n checkSorted(b);\n\n while (i_a < a.length && i_b < b.length) {\n if (a[i_a] === b[i_b]) {\n i_a++;\n i_b++;\n } else if (a[i_a] < b[i_b]) {\n a_only.push(a[i_a++]);\n } else {\n b_only.push(b[i_b++]);\n }\n }\n\n if (i_a < a.length)\n a_only = a_only.concat(a.slice(i_a));\n if (i_b < b.length)\n b_only = b_only.concat(b.slice(i_b));\n return {\n removed: a_only,\n added: b_only\n };\n}\n\n// Convert from wide: { colA: [1,2,3], colB: [4,5,6], ... }\n// to long: [ {colA: 1, colB: 4}, {colA: 2, colB: 5}, ... ]\nexport function dataframeToD3(df) {\n let names = [];\n let length;\n for (let name in df) {\n if (df.hasOwnProperty(name))\n names.push(name);\n if (typeof(df[name]) !== \"object\" || typeof(df[name].length) === \"undefined\") {\n throw new Error(\"All fields must be arrays\");\n } else if (typeof(length) !== \"undefined\" && length !== df[name].length) {\n throw new Error(\"All fields must be arrays of the same length\");\n }\n length = df[name].length;\n }\n let results = [];\n let item;\n for (let row = 0; row < length; row++) {\n item = {};\n for (let col = 0; col < names.length; col++) {\n item[names[col]] = df[names[col]][row];\n }\n results.push(item);\n }\n return results;\n}\n\n/**\n * Keeps track of all event listener additions/removals and lets all active\n * listeners be removed with a single operation.\n *\n * @private\n */\nexport class SubscriptionTracker {\n constructor(emitter) {\n this._emitter = emitter;\n this._subs = {};\n }\n\n on(eventType, listener) {\n let sub = this._emitter.on(eventType, listener);\n this._subs[sub] = eventType;\n return sub;\n }\n\n off(eventType, listener) {\n let sub = this._emitter.off(eventType, listener);\n if (sub) {\n delete this._subs[sub];\n }\n return sub;\n }\n\n removeAllListeners() {\n let current_subs = this._subs;\n this._subs = {};\n Object.keys(current_subs).forEach((sub) => {\n this._emitter.off(current_subs[sub], sub);\n });\n }\n}\n","import Events from \"./events\";\n\nexport default class Var {\n constructor(group, name, /*optional*/ value) {\n this._group = group;\n this._name = name;\n this._value = value;\n this._events = new Events();\n }\n\n get() {\n return this._value;\n }\n\n set(value, /*optional*/ event) {\n if (this._value === value) {\n // Do nothing; the value hasn't changed\n return;\n }\n let oldValue = this._value;\n this._value = value;\n // Alert JavaScript listeners that the value has changed\n let evt = {};\n if (event && typeof(event) === \"object\") {\n for (let k in event) {\n if (event.hasOwnProperty(k))\n evt[k] = event[k];\n }\n }\n evt.oldValue = oldValue;\n evt.value = value;\n this._events.trigger(\"change\", evt, this);\n\n // TODO: Make this extensible, to let arbitrary back-ends know that\n // something has changed\n if (global.Shiny && global.Shiny.onInputChange) {\n global.Shiny.onInputChange(\n \".clientValue-\" +\n (this._group.name !== null ? this._group.name + \"-\" : \"\") +\n this._name,\n typeof(value) === \"undefined\" ? null : value\n );\n }\n }\n\n on(eventType, listener) {\n return this._events.on(eventType, listener);\n }\n\n off(eventType, listener) {\n return this._events.off(eventType, listener);\n }\n}\n"]} -------------------------------------------------------------------------------- /libs/datatables-binding/datatables.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // some helper functions: using a global object DTWidget so that it can be used 4 | // in JS() code, e.g. datatable(options = list(foo = JS('code'))); unlike R's 5 | // dynamic scoping, when 'code' is eval()'ed, JavaScript does not know objects 6 | // from the "parent frame", e.g. JS('DTWidget') will not work unless it was made 7 | // a global object 8 | var DTWidget = {}; 9 | 10 | // 123456666.7890 -> 123,456,666.7890 11 | var markInterval = function(d, digits, interval, mark, decMark, precision) { 12 | x = precision ? d.toPrecision(digits) : d.toFixed(digits); 13 | if (!/^-?[\d.]+$/.test(x)) return x; 14 | var xv = x.split('.'); 15 | if (xv.length > 2) return x; // should have at most one decimal point 16 | xv[0] = xv[0].replace(new RegExp('\\B(?=(\\d{' + interval + '})+(?!\\d))', 'g'), mark); 17 | return xv.join(decMark); 18 | }; 19 | 20 | DTWidget.formatCurrency = function(thiz, row, data, col, currency, digits, interval, mark, decMark, before) { 21 | var d = parseFloat(data[col]); 22 | if (isNaN(d)) return; 23 | var res = markInterval(d, digits, interval, mark, decMark); 24 | res = before ? (/^-/.test(res) ? '-' + currency + res.replace(/^-/, '') : currency + res) : 25 | res + currency; 26 | $(thiz.api().cell(row, col).node()).html(res); 27 | }; 28 | 29 | DTWidget.formatString = function(thiz, row, data, col, prefix, suffix) { 30 | var d = data[col]; 31 | if (d === null) return; 32 | $(thiz.api().cell(row, col).node()).html(prefix + d + suffix); 33 | }; 34 | 35 | DTWidget.formatPercentage = function(thiz, row, data, col, digits, interval, mark, decMark) { 36 | var d = parseFloat(data[col]); 37 | if (isNaN(d)) return; 38 | $(thiz.api().cell(row, col).node()) 39 | .html(markInterval(d * 100, digits, interval, mark, decMark) + '%'); 40 | }; 41 | 42 | DTWidget.formatRound = function(thiz, row, data, col, digits, interval, mark, decMark) { 43 | var d = parseFloat(data[col]); 44 | if (isNaN(d)) return; 45 | $(thiz.api().cell(row, col).node()).html(markInterval(d, digits, interval, mark, decMark)); 46 | }; 47 | 48 | DTWidget.formatSignif = function(thiz, row, data, col, digits, interval, mark, decMark) { 49 | var d = parseFloat(data[col]); 50 | if (isNaN(d)) return; 51 | $(thiz.api().cell(row, col).node()) 52 | .html(markInterval(d, digits, interval, mark, decMark, true)); 53 | }; 54 | 55 | DTWidget.formatDate = function(thiz, row, data, col, method, params) { 56 | var d = data[col]; 57 | if (d === null) return; 58 | // (new Date('2015-10-28')).toDateString() may return 2015-10-27 because the 59 | // actual time created could be like 'Tue Oct 27 2015 19:00:00 GMT-0500 (CDT)', 60 | // i.e. the date-only string is treated as UTC time instead of local time 61 | if (method === 'toDateString' && /^\d{4,}\D\d{2}\D\d{2}$/.test(d)) { 62 | d = d.split(/\D/); 63 | d = new Date(d[0], d[1] - 1, d[2]); 64 | } else { 65 | d = new Date(d); 66 | } 67 | $(thiz.api().cell(row, col).node()).html(d[method].apply(d, params)); 68 | }; 69 | 70 | window.DTWidget = DTWidget; 71 | 72 | var transposeArray2D = function(a) { 73 | return a.length === 0 ? a : HTMLWidgets.transposeArray2D(a); 74 | }; 75 | 76 | var crosstalkPluginsInstalled = false; 77 | 78 | function maybeInstallCrosstalkPlugins() { 79 | if (crosstalkPluginsInstalled) 80 | return; 81 | crosstalkPluginsInstalled = true; 82 | 83 | $.fn.dataTable.ext.afnFiltering.push( 84 | function(oSettings, aData, iDataIndex) { 85 | var ctfilter = oSettings.nTable.ctfilter; 86 | if (ctfilter && !ctfilter[iDataIndex]) 87 | return false; 88 | 89 | var ctselect = oSettings.nTable.ctselect; 90 | if (ctselect && !ctselect[iDataIndex]) 91 | return false; 92 | 93 | return true; 94 | } 95 | ); 96 | } 97 | 98 | HTMLWidgets.widget({ 99 | name: "datatables", 100 | type: "output", 101 | renderOnNullValue: true, 102 | initialize: function(el, width, height) { 103 | $(el).html(' '); 104 | return { 105 | data: null, 106 | ctfilterHandle: new crosstalk.FilterHandle(), 107 | ctfilterSubscription: null, 108 | ctselectHandle: new crosstalk.SelectionHandle(), 109 | ctselectSubscription: null 110 | }; 111 | }, 112 | renderValue: function(el, data, instance) { 113 | if (el.offsetWidth === 0 || el.offsetHeight === 0) { 114 | instance.data = data; 115 | return; 116 | } 117 | instance.data = null; 118 | var $el = $(el); 119 | $el.empty(); 120 | 121 | if (data === null) { 122 | $el.append(' '); 123 | // clear previous Shiny inputs (if any) 124 | for (var i in instance.clearInputs) instance.clearInputs[i](); 125 | instance.clearInputs = {}; 126 | return; 127 | } 128 | 129 | var crosstalkOptions = data.crosstalkOptions; 130 | if (!crosstalkOptions) crosstalkOptions = { 131 | 'key': null, 'group': null 132 | }; 133 | if (crosstalkOptions.group) { 134 | maybeInstallCrosstalkPlugins(); 135 | instance.ctfilterHandle.setGroup(crosstalkOptions.group); 136 | instance.ctselectHandle.setGroup(crosstalkOptions.group); 137 | } 138 | 139 | // If we are in a flexdashboard scroll layout then we: 140 | // (a) Always want to use pagination (otherwise we'll have 141 | // a "double scroll bar" effect on the phone); and 142 | // (b) Never want to fill the container (we want the pagination 143 | // level to determine the size of the container) 144 | if (window.FlexDashboard && !window.FlexDashboard.isFillPage()) { 145 | data.options.bPaginate = true; 146 | data.fillContainer = false; 147 | } 148 | 149 | // if we are in the viewer then we always want to fillContainer and 150 | // and autoHideNavigation (unless the user has explicitly set these) 151 | if (window.HTMLWidgets.viewerMode) { 152 | if (!data.hasOwnProperty("fillContainer")) 153 | data.fillContainer = true; 154 | if (!data.hasOwnProperty("autoHideNavigation")) 155 | data.autoHideNavigation = true; 156 | } 157 | 158 | // propagate fillContainer to instance (so we have it in resize) 159 | instance.fillContainer = data.fillContainer; 160 | 161 | var cells = data.data; 162 | 163 | if (cells instanceof Array) cells = transposeArray2D(cells); 164 | 165 | $el.append(data.container); 166 | var $table = $el.find('table'); 167 | if (data.class) $table.addClass(data.class); 168 | if (data.caption) $table.prepend(data.caption); 169 | 170 | if (HTMLWidgets.shinyMode && data.selection.mode !== 'none' && 171 | data.selection.target === 'row+column') { 172 | if ($table.children('tfoot').length === 0) { 173 | $table.append($('')); 174 | $table.find('thead tr').clone().appendTo($table.find('tfoot')); 175 | } 176 | } 177 | 178 | // column filters 179 | var filterRow; 180 | switch (data.filter) { 181 | case 'top': 182 | $table.children('thead').append(data.filterHTML); 183 | filterRow = $table.find('thead tr:last td'); 184 | break; 185 | case 'bottom': 186 | if ($table.children('tfoot').length === 0) { 187 | $table.append($('')); 188 | } 189 | $table.children('tfoot').prepend(data.filterHTML); 190 | filterRow = $table.find('tfoot tr:first td'); 191 | break; 192 | } 193 | 194 | var options = { searchDelay: 1000 }; 195 | if (cells !== null) $.extend(options, { 196 | data: cells 197 | }); 198 | 199 | // options for fillContainer 200 | var bootstrapActive = typeof($.fn.popover) != 'undefined'; 201 | if (instance.fillContainer) { 202 | 203 | // force scrollX/scrollY and turn off autoWidth 204 | options.scrollX = true; 205 | options.scrollY = "100px"; // can be any value, we'll adjust below 206 | 207 | // if we aren't paginating then move around the info/filter controls 208 | // to save space at the bottom and rephrase the info callback 209 | if (data.options.bPaginate === false) { 210 | 211 | // we know how to do this cleanly for bootstrap, not so much 212 | // for other themes/layouts 213 | if (bootstrapActive) { 214 | options.dom = "<'row'<'col-sm-4'i><'col-sm-8'f>>" + 215 | "<'row'<'col-sm-12'tr>>"; 216 | } 217 | 218 | options.fnInfoCallback = function(oSettings, iStart, iEnd, 219 | iMax, iTotal, sPre) { 220 | return Number(iTotal).toLocaleString() + " records"; 221 | }; 222 | } 223 | } 224 | 225 | // auto hide navigation if requested 226 | if (data.autoHideNavigation === true) { 227 | if (bootstrapActive && data.options.bPaginate !== false) { 228 | // strip all nav if length >= cells 229 | if ((cells instanceof Array) && data.options.iDisplayLength >= cells.length) 230 | options.dom = "<'row'<'col-sm-12'tr>>"; 231 | // alternatively lean things out for flexdashboard mobile portrait 232 | else if (window.FlexDashboard && window.FlexDashboard.isMobilePhone()) 233 | options.dom = "<'row'<'col-sm-12'f>>" + 234 | "<'row'<'col-sm-12'tr>>" + 235 | "<'row'<'col-sm-12'p>>"; 236 | } 237 | } 238 | 239 | $.extend(true, options, data.options || {}); 240 | 241 | var searchCols = options.searchCols; 242 | if (searchCols) { 243 | searchCols = searchCols.map(function(x) { 244 | return x === null ? '' : x.search; 245 | }); 246 | // FIXME: this means I don't respect the escapeRegex setting 247 | delete options.searchCols; 248 | } 249 | 250 | // server-side processing? 251 | var server = options.serverSide === true; 252 | 253 | // use the dataSrc function to pre-process JSON data returned from R 254 | var DT_rows_all = [], DT_rows_current = []; 255 | if (server && HTMLWidgets.shinyMode && typeof options.ajax === 'object' && 256 | /^session\/[\da-z]+\/dataobj/.test(options.ajax.url) && !options.ajax.dataSrc) { 257 | options.ajax.dataSrc = function(json) { 258 | DT_rows_all = $.makeArray(json.DT_rows_all); 259 | DT_rows_current = $.makeArray(json.DT_rows_current); 260 | return json.data; 261 | }; 262 | } 263 | 264 | var thiz = this; 265 | if (instance.fillContainer) $table.on('init.dt', function(e) { 266 | thiz.fillAvailableHeight(el, $(el).innerHeight()); 267 | }); 268 | 269 | var table = $table.DataTable(options); 270 | $el.data('datatable', table); 271 | 272 | // Unregister previous Crosstalk event subscriptions, if they exist 273 | if (instance.ctfilterSubscription) { 274 | instance.ctfilterHandle.off("change", instance.ctfilterSubscription); 275 | instance.ctfilterSubscription = null; 276 | } 277 | if (instance.ctselectSubscription) { 278 | instance.ctselectHandle.off("change", instance.ctselectSubscription); 279 | instance.ctselectSubscription = null; 280 | } 281 | 282 | if (!crosstalkOptions.group) { 283 | $table[0].ctfilter = null; 284 | $table[0].ctselect = null; 285 | } else { 286 | var key = crosstalkOptions.key; 287 | function keysToMatches(keys) { 288 | if (!keys) { 289 | return null; 290 | } else { 291 | var selectedKeys = {}; 292 | for (var i = 0; i < keys.length; i++) { 293 | selectedKeys[keys[i]] = true; 294 | } 295 | var matches = {}; 296 | for (var j = 0; j < key.length; j++) { 297 | if (selectedKeys[key[j]]) 298 | matches[j] = true; 299 | } 300 | return matches; 301 | } 302 | } 303 | 304 | function applyCrosstalkFilter(e) { 305 | $table[0].ctfilter = keysToMatches(e.value); 306 | table.draw(); 307 | } 308 | instance.ctfilterSubscription = instance.ctfilterHandle.on("change", applyCrosstalkFilter); 309 | applyCrosstalkFilter({value: instance.ctfilterHandle.filteredKeys}); 310 | 311 | function applyCrosstalkSelection(e) { 312 | if (e.sender !== instance.ctselectHandle) { 313 | table 314 | .rows('.' + selClass, {search: 'applied'}) 315 | .nodes() 316 | .to$() 317 | .removeClass(selClass); 318 | if (selectedRows) 319 | changeInput('rows_selected', selectedRows(), void 0, true); 320 | } 321 | 322 | if (e.sender !== instance.ctselectHandle && e.value && e.value.length) { 323 | $table[0].ctselect = keysToMatches(e.value); 324 | table.draw(); 325 | } else { 326 | if ($table[0].ctselect) { 327 | $table[0].ctselect = null; 328 | table.draw(); 329 | } 330 | } 331 | } 332 | instance.ctselectSubscription = instance.ctselectHandle.on("change", applyCrosstalkSelection); 333 | // TODO: This next line doesn't seem to work when renderDataTable is used 334 | applyCrosstalkSelection({value: instance.ctselectHandle.value}); 335 | } 336 | 337 | var inArray = function(val, array) { 338 | return $.inArray(val, $.makeArray(array)) > -1; 339 | }; 340 | 341 | // encode + to %2B when searching in the table on server side, because 342 | // shiny::parseQueryString() treats + as spaces, and DataTables does not 343 | // encode + to %2B (or % to %25) when sending the request 344 | var encode_plus = function(x) { 345 | return server ? x.replace(/%/g, '%25').replace(/\+/g, '%2B') : x; 346 | }; 347 | 348 | // search the i-th column 349 | var searchColumn = function(i, value) { 350 | var regex = false, ci = true; 351 | if (options.search) { 352 | regex = options.search.regex, 353 | ci = options.search.caseInsensitive !== false; 354 | } 355 | return table.column(i).search(encode_plus(value), regex, !regex, ci); 356 | }; 357 | 358 | if (data.filter !== 'none') { 359 | 360 | filterRow.each(function(i, td) { 361 | 362 | var $td = $(td), type = $td.data('type'), filter; 363 | var $input = $td.children('div').first().children('input'); 364 | $input.prop('disabled', !table.settings()[0].aoColumns[i].bSearchable || type === 'disabled'); 365 | $input.on('input blur', function() { 366 | $input.next('span').toggle(Boolean($input.val())); 367 | }); 368 | // Bootstrap sets pointer-events to none and we won't be able to click 369 | // the clear button 370 | $input.next('span').css('pointer-events', 'auto').hide().click(function() { 371 | $(this).hide().prev('input').val('').trigger('input').focus(); 372 | }); 373 | var searchCol; // search string for this column 374 | if (searchCols && searchCols[i]) { 375 | searchCol = searchCols[i]; 376 | $input.val(searchCol).trigger('input'); 377 | } 378 | var $x = $td.children('div').last(); 379 | 380 | // remove the overflow: hidden attribute of the scrollHead 381 | // (otherwise the scrolling table body obscures the filters) 382 | var scrollHead = $(el).find('.dataTables_scrollHead,.dataTables_scrollFoot'); 383 | var cssOverflow = scrollHead.css('overflow'); 384 | if (cssOverflow === 'hidden') { 385 | $x.on('show hide', function(e) { 386 | scrollHead.css('overflow', e.type === 'show' ? '' : cssOverflow); 387 | }); 388 | $x.css('z-index', 25); 389 | } 390 | 391 | if (inArray(type, ['factor', 'logical'])) { 392 | $input.on({ 393 | click: function() { 394 | $input.parent().hide(); $x.show().trigger('show'); filter[0].selectize.focus(); 395 | }, 396 | input: function() { 397 | if ($input.val() === '') filter[0].selectize.setValue([]); 398 | } 399 | }); 400 | var $input2 = $x.children('select'); 401 | filter = $input2.selectize({ 402 | options: $input2.data('options').map(function(v, i) { 403 | return ({text: v, value: v}); 404 | }), 405 | plugins: ['remove_button'], 406 | hideSelected: true, 407 | onChange: function(value) { 408 | if (value === null) value = []; // compatibility with jQuery 3.0 409 | $input.val(value.length ? JSON.stringify(value) : ''); 410 | if (value.length) $input.trigger('input'); 411 | $input.attr('title', $input.val()); 412 | if (server) { 413 | table.column(i).search(value.length ? encode_plus(JSON.stringify(value)) : '').draw(); 414 | return; 415 | } 416 | // turn off filter if nothing selected 417 | $td.data('filter', value.length > 0); 418 | table.draw(); // redraw table, and filters will be applied 419 | } 420 | }); 421 | if (searchCol) filter[0].selectize.setValue(JSON.parse(searchCol)); 422 | // an ugly hack to deal with shiny: for some reason, the onBlur event 423 | // of selectize does not work in shiny 424 | $x.find('div > div.selectize-input > input').on('blur', function() { 425 | $x.hide().trigger('hide'); $input.parent().show(); $input.trigger('blur'); 426 | }); 427 | filter.next('div').css('margin-bottom', 'auto'); 428 | } else if (type === 'character') { 429 | var fun = function() { 430 | searchColumn(i, $input.val()).draw(); 431 | }; 432 | if (server) { 433 | fun = $.fn.dataTable.util.throttle(fun, options.searchDelay); 434 | } 435 | $input.on('input', fun); 436 | } else if (inArray(type, ['number', 'integer', 'date', 'time'])) { 437 | var $x0 = $x; 438 | $x = $x0.children('div').first(); 439 | $x0.css({ 440 | 'background-color': '#fff', 441 | 'border': '1px #ddd solid', 442 | 'border-radius': '4px', 443 | 'padding': '20px 20px 10px 20px' 444 | }); 445 | var $spans = $x0.children('span').css({ 446 | 'margin-top': '10px', 447 | 'white-space': 'nowrap' 448 | }); 449 | var $span1 = $spans.first(), $span2 = $spans.last(); 450 | var r1 = +$x.data('min'), r2 = +$x.data('max'); 451 | // when the numbers are too small or have many decimal places, the 452 | // slider may have numeric precision problems (#150) 453 | var scale = Math.pow(10, Math.max(0, +$x.data('scale') || 0)); 454 | r1 = Math.round(r1 * scale); r2 = Math.round(r2 * scale); 455 | var scaleBack = function(x, scale) { 456 | if (scale === 1) return x; 457 | var d = Math.round(Math.log(scale) / Math.log(10)); 458 | // to avoid problems like 3.423/100 -> 0.034230000000000003 459 | return (x / scale).toFixed(d); 460 | }; 461 | $input.on({ 462 | focus: function() { 463 | $x0.show().trigger('show'); 464 | // first, make sure the slider div leaves at least 20px between 465 | // the two (slider value) span's 466 | $x0.width(Math.max(160, $span1.outerWidth() + $span2.outerWidth() + 20)); 467 | // then, if the input is really wide, make the slider the same 468 | // width as the input 469 | if ($x0.outerWidth() < $input.outerWidth()) { 470 | $x0.outerWidth($input.outerWidth()); 471 | } 472 | // make sure the slider div does not reach beyond the right margin 473 | if ($(window).width() < $x0.offset().left + $x0.width()) { 474 | $x0.offset({ 475 | 'left': $input.offset().left + $input.outerWidth() - $x0.outerWidth() 476 | }); 477 | } 478 | }, 479 | blur: function() { 480 | $x0.hide().trigger('hide'); 481 | }, 482 | input: function() { 483 | if ($input.val() === '') filter.val([r1, r2]); 484 | }, 485 | change: function() { 486 | var v = $input.val().replace(/\s/g, ''); 487 | if (v === '') return; 488 | v = v.split('...'); 489 | if (v.length !== 2) { 490 | $input.parent().addClass('has-error'); 491 | return; 492 | } 493 | if (v[0] === '') v[0] = r1; 494 | if (v[1] === '') v[1] = r2; 495 | $input.parent().removeClass('has-error'); 496 | // treat date as UTC time at midnight 497 | var strTime = function(x) { 498 | var s = type === 'date' ? 'T00:00:00Z' : ''; 499 | var t = new Date(x + s).getTime(); 500 | // add 10 minutes to date since it does not hurt the date, and 501 | // it helps avoid the tricky floating point arithmetic problems, 502 | // e.g. sometimes the date may be a few milliseconds earlier 503 | // than the midnight due to precision problems in noUiSlider 504 | return type === 'date' ? t + 3600000 : t; 505 | }; 506 | if (inArray(type, ['date', 'time'])) { 507 | v[0] = strTime(v[0]); 508 | v[1] = strTime(v[1]); 509 | } 510 | if (v[0] != r1) v[0] *= scale; 511 | if (v[1] != r2) v[1] *= scale; 512 | filter.val(v); 513 | } 514 | }); 515 | var formatDate = function(d) { 516 | d = scaleBack(d, scale); 517 | if (type === 'number') return d; 518 | if (type === 'integer') return parseInt(d); 519 | var x = new Date(+d); 520 | if (type === 'date') { 521 | var pad0 = function(x) { 522 | return ('0' + x).substr(-2, 2); 523 | }; 524 | return x.getUTCFullYear() + '-' + pad0(1 + x.getUTCMonth()) 525 | + '-' + pad0(x.getUTCDate()); 526 | } else { 527 | return x.toISOString(); 528 | } 529 | }; 530 | var opts = type === 'date' ? { step: 60 * 60 * 1000 } : 531 | type === 'integer' ? { step: 1 } : {}; 532 | filter = $x.noUiSlider($.extend({ 533 | start: [r1, r2], 534 | range: {min: r1, max: r2}, 535 | connect: true 536 | }, opts)); 537 | if (scale > 1) (function() { 538 | var t1 = r1, t2 = r2; 539 | var val = filter.val(); 540 | while (val[0] > r1 || val[1] < r2) { 541 | if (val[0] > r1) { 542 | t1 -= val[0] - r1; 543 | } 544 | if (val[1] < r2) { 545 | t2 += r2 - val[1]; 546 | } 547 | filter = $x.noUiSlider($.extend({ 548 | start: [t1, t2], 549 | range: {min: t1, max: t2}, 550 | connect: true 551 | }, opts), true); 552 | val = filter.val(); 553 | } 554 | r1 = t1; r2 = t2; 555 | })(); 556 | $span1.text(formatDate(r1)); $span2.text(formatDate(r2)); 557 | var updateSlider = function(e) { 558 | var val = filter.val(); 559 | // turn off filter if in full range 560 | $td.data('filter', val[0] > r1 || val[1] < r2); 561 | var v1 = formatDate(val[0]), v2 = formatDate(val[1]), ival; 562 | if ($td.data('filter')) { 563 | ival = v1 + ' ... ' + v2; 564 | $input.attr('title', ival).val(ival).trigger('input'); 565 | } else { 566 | $input.attr('title', '').val(''); 567 | } 568 | $span1.text(v1); $span2.text(v2); 569 | if (e.type === 'slide') return; // no searching when sliding only 570 | if (server) { 571 | table.column(i).search($td.data('filter') ? ival : '').draw(); 572 | return; 573 | } 574 | table.draw(); 575 | }; 576 | filter.on({ 577 | set: updateSlider, 578 | slide: updateSlider 579 | }); 580 | } 581 | 582 | // server-side processing will be handled by R (or whatever server 583 | // language you use); the following code is only needed for client-side 584 | // processing 585 | if (server) { 586 | // if a search string has been pre-set, search now 587 | if (searchCol) searchColumn(i, searchCol).draw(); 588 | return; 589 | } 590 | 591 | var customFilter = function(settings, data, dataIndex) { 592 | // there is no way to attach a search function to a specific table, 593 | // and we need to make sure a global search function is not applied to 594 | // all tables (i.e. a range filter in a previous table should not be 595 | // applied to the current table); we use the settings object to 596 | // determine if we want to perform searching on the current table, 597 | // since settings.sTableId will be different to different tables 598 | if (table.settings()[0] !== settings) return true; 599 | // no filter on this column or no need to filter this column 600 | if (typeof filter === 'undefined' || !$td.data('filter')) return true; 601 | 602 | var r = filter.val(), v, r0, r1; 603 | if (type === 'number' || type === 'integer') { 604 | v = parseFloat(data[i]); 605 | // how to handle NaN? currently exclude these rows 606 | if (isNaN(v)) return(false); 607 | r0 = parseFloat(scaleBack(r[0], scale)) 608 | r1 = parseFloat(scaleBack(r[1], scale)); 609 | if (v >= r0 && v <= r1) return true; 610 | } else if (type === 'date' || type === 'time') { 611 | v = new Date(data[i]); 612 | r0 = new Date(r[0] / scale); r1 = new Date(r[1] / scale); 613 | if (v >= r0 && v <= r1) return true; 614 | } else if (type === 'factor') { 615 | if (r.length === 0 || inArray(data[i], r)) return true; 616 | } else if (type === 'logical') { 617 | if (r.length === 0) return true; 618 | if (inArray(data[i] === '' ? 'na' : data[i], r)) return true; 619 | } 620 | return false; 621 | }; 622 | 623 | $.fn.dataTable.ext.search.push(customFilter); 624 | 625 | // search for the preset search strings if it is non-empty 626 | if (searchCol) { 627 | if (inArray(type, ['factor', 'logical'])) { 628 | filter[0].selectize.setValue(JSON.parse(searchCol)); 629 | } else if (type === 'character') { 630 | $input.trigger('input'); 631 | } else if (inArray(type, ['number', 'integer', 'date', 'time'])) { 632 | $input.trigger('change'); 633 | } 634 | } 635 | 636 | }); 637 | 638 | } 639 | 640 | // highlight search keywords 641 | var highlight = function() { 642 | var body = $(table.table().body()); 643 | // removing the old highlighting first 644 | body.unhighlight(); 645 | 646 | // don't highlight the "not found" row, so we get the rows using the api 647 | if (table.rows({ filter: 'applied' }).data().length === 0) return; 648 | // highlight gloal search keywords 649 | body.highlight($.trim(table.search()).split(/\s+/)); 650 | // then highlight keywords from individual column filters 651 | if (filterRow) filterRow.each(function(i, td) { 652 | var $td = $(td), type = $td.data('type'); 653 | if (type !== 'character') return; 654 | var $input = $td.children('div').first().children('input'); 655 | var column = table.column(i).nodes().to$(), 656 | val = $.trim($input.val()); 657 | if (type !== 'character' || val === '') return; 658 | column.highlight(val.split(/\s+/)); 659 | }); 660 | }; 661 | 662 | if (options.searchHighlight) { 663 | table 664 | .on('draw.dt.dth column-visibility.dt.dth column-reorder.dt.dth', highlight) 665 | .on('destroy', function() { 666 | // remove event handler 667 | table.off('draw.dt.dth column-visibility.dt.dth column-reorder.dt.dth'); 668 | }); 669 | 670 | // initial highlight for state saved conditions and initial states 671 | highlight(); 672 | } 673 | 674 | // run the callback function on the table instance 675 | if (typeof data.callback === 'function') data.callback(table); 676 | 677 | // double click to edit the cell 678 | if (data.editable) table.on('dblclick.dt', 'tbody td', function() { 679 | var $input = $(''); 680 | var $this = $(this), value = table.cell(this).data(), html = $this.html(); 681 | var changed = false; 682 | $input.val(value); 683 | $this.empty().append($input); 684 | $input.css('width', '100%').focus().on('change', function() { 685 | changed = true; 686 | var valueNew = $input.val(); 687 | if (valueNew != value) { 688 | table.cell($this).data(valueNew); 689 | if (HTMLWidgets.shinyMode) changeInput('cell_edit', cellInfo($this)); 690 | // for server-side processing, users have to call replaceData() to update the table 691 | if (!server) table.draw(false); 692 | } else { 693 | $this.html(html); 694 | } 695 | $input.remove(); 696 | }).on('blur', function() { 697 | if (!changed) $input.trigger('change'); 698 | }); 699 | }); 700 | 701 | // interaction with shiny 702 | if (!HTMLWidgets.shinyMode && !crosstalkOptions.group) return; 703 | 704 | var methods = {}; 705 | var shinyData = {}; 706 | 707 | methods.updateCaption = function(caption) { 708 | if (!caption) return; 709 | $table.children('caption').replaceWith(caption); 710 | } 711 | 712 | // register clear functions to remove input values when the table is removed 713 | instance.clearInputs = {}; 714 | 715 | var changeInput = function(id, value, type, noCrosstalk) { 716 | var event = id; 717 | id = el.id + '_' + id; 718 | if (type) id = id + ':' + type; 719 | // do not update if the new value is the same as old value 720 | if (shinyData.hasOwnProperty(id) && shinyData[id] === JSON.stringify(value)) 721 | return; 722 | shinyData[id] = JSON.stringify(value); 723 | if (HTMLWidgets.shinyMode) { 724 | Shiny.onInputChange(id, value); 725 | if (!instance.clearInputs[id]) instance.clearInputs[id] = function() { 726 | Shiny.onInputChange(id, null); 727 | } 728 | } 729 | 730 | // HACK 731 | if (event === "rows_selected" && !noCrosstalk) { 732 | if (crosstalkOptions.group) { 733 | var keys = crosstalkOptions.key; 734 | var selectedKeys = null; 735 | if (value) { 736 | selectedKeys = []; 737 | for (var i = 0; i < value.length; i++) { 738 | // The value array's contents use 1-based row numbers, so we must 739 | // convert to 0-based before indexing into the keys array. 740 | selectedKeys.push(keys[value[i] - 1]); 741 | } 742 | } 743 | instance.ctselectHandle.set(selectedKeys); 744 | } 745 | } 746 | }; 747 | 748 | var addOne = function(x) { 749 | return x.map(function(i) { return 1 + i; }); 750 | }; 751 | 752 | var unique = function(x) { 753 | var ux = []; 754 | $.each(x, function(i, el){ 755 | if ($.inArray(el, ux) === -1) ux.push(el); 756 | }); 757 | return ux; 758 | } 759 | 760 | // change the row index of a cell 761 | var tweakCellIndex = function(cell) { 762 | var info = cell.index(); 763 | if (server) { 764 | info.row = DT_rows_current[info.row]; 765 | } else { 766 | info.row += 1; 767 | } 768 | return {row: info.row, col: info.column}; 769 | } 770 | 771 | var selMode = data.selection.mode, selTarget = data.selection.target; 772 | if (inArray(selMode, ['single', 'multiple'])) { 773 | var selClass = data.style === 'bootstrap' ? 'active' : 'selected'; 774 | var selected = data.selection.selected, selected1, selected2; 775 | // selected1: row indices; selected2: column indices 776 | if (selected === null) { 777 | selected1 = selected2 = []; 778 | } else if (selTarget === 'row') { 779 | selected1 = $.makeArray(selected); 780 | } else if (selTarget === 'column') { 781 | selected2 = $.makeArray(selected); 782 | } else if (selTarget === 'row+column') { 783 | selected1 = $.makeArray(selected.rows); 784 | selected2 = $.makeArray(selected.cols); 785 | } 786 | 787 | // After users reorder the rows or filter the table, we cannot use the table index 788 | // directly. Instead, we need this function to find out the rows between the two clicks. 789 | // If user filter the table again between the start click and the end click, the behavior 790 | // would be undefined, but it should not be a problem. 791 | var shiftSelRowsIndex = function(start, end) { 792 | var indexes = server ? DT_rows_all : table.rows({ search: 'applied' }).indexes().toArray(); 793 | start = indexes.indexOf(start); end = indexes.indexOf(end); 794 | // if start is larger than end, we need to swap 795 | if (start > end) { 796 | var tmp = end; end = start; start = tmp; 797 | } 798 | return indexes.slice(start, end + 1); 799 | } 800 | 801 | var serverRowIndex = function(clientRowIndex) { 802 | return server ? DT_rows_current[clientRowIndex] : clientRowIndex + 1; 803 | } 804 | 805 | // row, column, or cell selection 806 | var lastClickedRow; 807 | if (inArray(selTarget, ['row', 'row+column'])) { 808 | var selectedRows = function() { 809 | var rows = table.rows('.' + selClass); 810 | var idx = rows.indexes().toArray(); 811 | if (!server) return addOne(idx); 812 | idx = idx.map(function(i) { 813 | return DT_rows_current[i]; 814 | }); 815 | selected1 = selMode === 'multiple' ? unique(selected1.concat(idx)) : idx; 816 | return selected1; 817 | } 818 | table.on('mousedown.dt', 'tbody tr', function(e) { 819 | var $this = $(this), thisRow = table.row(this); 820 | if (selMode === 'multiple') { 821 | if (e.shiftKey && lastClickedRow !== undefined) { 822 | // select or de-select depends on the last clicked row's status 823 | var flagSel = !$this.hasClass(selClass); 824 | var crtClickedRow = serverRowIndex(thisRow.index()); 825 | if (server) { 826 | var rowsIndex = shiftSelRowsIndex(lastClickedRow, crtClickedRow); 827 | // update current page's selClass 828 | rowsIndex.map(function(i) { 829 | var rowIndex = DT_rows_current.indexOf(i); 830 | if (rowIndex >= 0) { 831 | var row = table.row(rowIndex).nodes().to$(); 832 | var flagRowSel = !row.hasClass(selClass); 833 | if (flagSel === flagRowSel) row.toggleClass(selClass); 834 | } 835 | }); 836 | // update selected1 837 | if (flagSel) { 838 | selected1 = unique(selected1.concat(rowsIndex)); 839 | } else { 840 | selected1 = selected1.filter(function(index) { 841 | return !inArray(index, rowsIndex); 842 | }); 843 | } 844 | } else { 845 | // js starts from 0 846 | shiftSelRowsIndex(lastClickedRow - 1, crtClickedRow - 1).map(function(value) { 847 | var row = table.row(value).nodes().to$(); 848 | var flagRowSel = !row.hasClass(selClass); 849 | if (flagSel === flagRowSel) row.toggleClass(selClass); 850 | }); 851 | } 852 | e.preventDefault(); 853 | } else { 854 | $this.toggleClass(selClass); 855 | } 856 | } else { 857 | if ($this.hasClass(selClass)) { 858 | $this.removeClass(selClass); 859 | } else { 860 | table.$('tr.' + selClass).removeClass(selClass); 861 | $this.addClass(selClass); 862 | } 863 | } 864 | if (server && !$this.hasClass(selClass)) { 865 | var id = DT_rows_current[thisRow.index()]; 866 | // remove id from selected1 since its class .selected has been removed 867 | if (inArray(id, selected1)) selected1.splice($.inArray(id, selected1), 1); 868 | } 869 | changeInput('rows_selected', selectedRows()); 870 | changeInput('row_last_clicked', serverRowIndex(thisRow.index())); 871 | lastClickedRow = serverRowIndex(thisRow.index()); 872 | }); 873 | changeInput('rows_selected', selected1); 874 | var selectRows = function() { 875 | table.$('tr.' + selClass).removeClass(selClass); 876 | if (selected1.length === 0) return; 877 | if (server) { 878 | table.rows({page: 'current'}).every(function() { 879 | if (inArray(DT_rows_current[this.index()], selected1)) { 880 | $(this.node()).addClass(selClass); 881 | } 882 | }); 883 | } else { 884 | var selected0 = selected1.map(function(i) { return i - 1; }); 885 | $(table.rows(selected0).nodes()).addClass(selClass); 886 | } 887 | } 888 | selectRows(); // in case users have specified pre-selected rows 889 | // restore selected rows after the table is redrawn (e.g. sort/search/page); 890 | // client-side tables will preserve the selections automatically; for 891 | // server-side tables, we have to *real* row indices are in `selected1` 892 | if (server) table.on('draw.dt', selectRows); 893 | methods.selectRows = function(selected) { 894 | selected1 = selected ? selected : []; 895 | selectRows(); 896 | changeInput('rows_selected', selected1); 897 | } 898 | } 899 | 900 | if (inArray(selTarget, ['column', 'row+column'])) { 901 | if (selTarget === 'row+column') { 902 | $(table.columns().footer()).css('cursor', 'pointer'); 903 | } 904 | table.on('click.dt', selTarget === 'column' ? 'tbody td' : 'tfoot tr th', function() { 905 | var colIdx = selTarget === 'column' ? table.cell(this).index().column : 906 | $.inArray(this, table.columns().footer()), 907 | thisCol = $(table.column(colIdx).nodes()); 908 | if (colIdx === -1) return; 909 | if (thisCol.hasClass(selClass)) { 910 | thisCol.removeClass(selClass); 911 | selected2.splice($.inArray(colIdx, selected2), 1); 912 | } else { 913 | if (selMode === 'single') $(table.cells().nodes()).removeClass(selClass); 914 | thisCol.addClass(selClass); 915 | selected2 = selMode === 'single' ? [colIdx] : unique(selected2.concat([colIdx])); 916 | } 917 | changeInput('columns_selected', selected2); 918 | }); 919 | changeInput('columns_selected', selected2); 920 | var selectCols = function() { 921 | table.columns().nodes().flatten().to$().removeClass(selClass); 922 | if (selected2.length > 0) 923 | table.columns(selected2).nodes().flatten().to$().addClass(selClass); 924 | } 925 | selectCols(); // in case users have specified pre-selected columns 926 | if (server) table.on('draw.dt', selectCols); 927 | methods.selectColumns = function(selected) { 928 | selected2 = selected ? selected : []; 929 | selectCols(); 930 | changeInput('columns_selected', selected2); 931 | } 932 | } 933 | 934 | if (selTarget === 'cell') { 935 | var selected3; 936 | if (selected === null) { 937 | selected3 = []; 938 | } else { 939 | selected3 = selected; 940 | } 941 | var findIndex = function(ij) { 942 | for (var i = 0; i < selected3.length; i++) { 943 | if (ij[0] === selected3[i][0] && ij[1] === selected3[i][1]) return i; 944 | } 945 | return -1; 946 | } 947 | table.on('click.dt', 'tbody td', function() { 948 | var $this = $(this), info = tweakCellIndex(table.cell(this)); 949 | if ($this.hasClass(selClass)) { 950 | $this.removeClass(selClass); 951 | selected3.splice(findIndex([info.row, info.col]), 1); 952 | } else { 953 | if (selMode === 'single') $(table.cells().nodes()).removeClass(selClass); 954 | $this.addClass(selClass); 955 | selected3 = selMode === 'single' ? [[info.row, info.col]] : 956 | unique(selected3.concat([[info.row, info.col]])); 957 | } 958 | changeInput('cells_selected', transposeArray2D(selected3), 'shiny.matrix'); 959 | }); 960 | changeInput('cells_selected', transposeArray2D(selected3), 'shiny.matrix'); 961 | var selectCells = function() { 962 | table.$('td.' + selClass).removeClass(selClass); 963 | if (selected3.length === 0) return; 964 | if (server) { 965 | table.cells({page: 'current'}).every(function() { 966 | var info = tweakCellIndex(this); 967 | if (findIndex([info.row, info.col], selected3) > -1) 968 | $(this.node()).addClass(selClass); 969 | }); 970 | } else { 971 | selected3.map(function(ij) { 972 | $(table.cell(ij[0] - 1, ij[1]).node()).addClass(selClass); 973 | }); 974 | } 975 | }; 976 | selectCells(); // in case users have specified pre-selected columns 977 | if (server) table.on('draw.dt', selectCells); 978 | methods.selectCells = function(selected) { 979 | selected3 = selected ? selected : []; 980 | selectCells(); 981 | changeInput('cells_selected', transposeArray2D(selected3), 'shiny.matrix'); 982 | } 983 | } 984 | } 985 | 986 | // expose some table info to Shiny 987 | var updateTableInfo = function(e, settings) { 988 | // TODO: is anyone interested in the page info? 989 | // changeInput('page_info', table.page.info()); 990 | var updateRowInfo = function(id, modifier) { 991 | var idx; 992 | if (server) { 993 | idx = modifier.page === 'current' ? DT_rows_current : DT_rows_all; 994 | } else { 995 | var rows = table.rows($.extend({ 996 | search: 'applied', 997 | page: 'all' 998 | }, modifier)); 999 | idx = addOne(rows.indexes().toArray()); 1000 | } 1001 | changeInput('rows' + '_' + id, idx); 1002 | }; 1003 | updateRowInfo('current', {page: 'current'}); 1004 | updateRowInfo('all', {}); 1005 | } 1006 | table.on('draw.dt', updateTableInfo); 1007 | updateTableInfo(); 1008 | 1009 | // state info 1010 | table.on('draw.dt column-visibility.dt', function() { 1011 | changeInput('state', table.state()); 1012 | }); 1013 | changeInput('state', table.state()); 1014 | 1015 | // search info 1016 | var updateSearchInfo = function() { 1017 | changeInput('search', table.search()); 1018 | if (filterRow) changeInput('search_columns', filterRow.toArray().map(function(td) { 1019 | return $(td).find('input').first().val(); 1020 | })); 1021 | } 1022 | table.on('draw.dt', updateSearchInfo); 1023 | updateSearchInfo(); 1024 | 1025 | var cellInfo = function(thiz) { 1026 | var info = tweakCellIndex(table.cell(thiz)); 1027 | info.value = table.cell(thiz).data(); 1028 | return info; 1029 | } 1030 | // the current cell clicked on 1031 | table.on('click.dt', 'tbody td', function() { 1032 | changeInput('cell_clicked', cellInfo(this)); 1033 | }) 1034 | changeInput('cell_clicked', {}); 1035 | 1036 | // do not trigger table selection when clicking on links unless they have classes 1037 | table.on('click.dt', 'tbody td a', function(e) { 1038 | if (this.className === '') e.stopPropagation(); 1039 | }); 1040 | 1041 | methods.addRow = function(data, rowname) { 1042 | var data0 = table.row(0).data(), n = data0.length, d = n - data.length; 1043 | if (d === 1) { 1044 | data = rowname.concat(data) 1045 | } else if (d !== 0) { 1046 | console.log(data); 1047 | console.log(data0); 1048 | throw 'New data must be of the same length as current data (' + n + ')'; 1049 | }; 1050 | table.row.add(data).draw(); 1051 | } 1052 | 1053 | methods.updateSearch = function(keywords) { 1054 | if (keywords.global !== null) 1055 | $(table.table().container()).find('input[type=search]').first() 1056 | .val(keywords.global).trigger('input'); 1057 | var columns = keywords.columns; 1058 | if (!filterRow || columns === null) return; 1059 | filterRow.toArray().map(function(td, i) { 1060 | var v = typeof columns === 'string' ? columns : columns[i]; 1061 | if (typeof v === 'undefined') { 1062 | console.log('The search keyword for column ' + i + ' is undefined') 1063 | return; 1064 | } 1065 | $(td).find('input').first().val(v); 1066 | searchColumn(i, v); 1067 | }); 1068 | table.draw(); 1069 | } 1070 | 1071 | methods.selectPage = function(page) { 1072 | if (table.page.info().pages < page || page < 1) { 1073 | throw 'Selected page is out of range'; 1074 | }; 1075 | table.page(page - 1).draw(false); 1076 | } 1077 | 1078 | methods.reloadData = function(resetPaging, clearSelection) { 1079 | // empty selections first if necessary 1080 | if (methods.selectRows && inArray('row', clearSelection)) methods.selectRows([]); 1081 | if (methods.selectColumns && inArray('column', clearSelection)) methods.selectColumns([]); 1082 | if (methods.selectCells && inArray('cell', clearSelection)) methods.selectCells([]); 1083 | table.ajax.reload(null, resetPaging); 1084 | } 1085 | 1086 | table.shinyMethods = methods; 1087 | }, 1088 | resize: function(el, width, height, instance) { 1089 | if (instance.data) this.renderValue(el, instance.data, instance); 1090 | 1091 | // dynamically adjust height if fillContainer = TRUE 1092 | if (instance.fillContainer) 1093 | this.fillAvailableHeight(el, height); 1094 | 1095 | this.adjustWidth(el); 1096 | }, 1097 | 1098 | // dynamically set the scroll body to fill available height 1099 | // (used with fillContainer = TRUE) 1100 | fillAvailableHeight: function(el, availableHeight) { 1101 | 1102 | // see how much of the table is occupied by header/footer elements 1103 | // and use that to compute a target scroll body height 1104 | var dtWrapper = $(el).find('div.dataTables_wrapper'); 1105 | var dtScrollBody = $(el).find($('div.dataTables_scrollBody')); 1106 | var framingHeight = dtWrapper.innerHeight() - dtScrollBody.innerHeight(); 1107 | var scrollBodyHeight = availableHeight - framingHeight; 1108 | 1109 | // set the height 1110 | dtScrollBody.height(scrollBodyHeight + 'px'); 1111 | }, 1112 | 1113 | // adjust the width of columns; remove the hard-coded widths on table and the 1114 | // scroll header when scrollX/Y are enabled 1115 | adjustWidth: function(el) { 1116 | var $el = $(el), table = $el.data('datatable'); 1117 | if (table) table.columns.adjust(); 1118 | $el.find('.dataTables_scrollHeadInner').css('width', '') 1119 | .children('table').css('margin-left', ''); 1120 | } 1121 | }); 1122 | 1123 | if (!HTMLWidgets.shinyMode) return; 1124 | 1125 | Shiny.addCustomMessageHandler('datatable-calls', function(data) { 1126 | var id = data.id; 1127 | var el = document.getElementById(id); 1128 | var table = el ? $(el).data('datatable') : null; 1129 | if (!table) { 1130 | console.log("Couldn't find table with id " + id); 1131 | return; 1132 | } 1133 | 1134 | var methods = table.shinyMethods, call = data.call; 1135 | if (methods[call.method]) { 1136 | methods[call.method].apply(table, call.args); 1137 | } else { 1138 | console.log("Unknown method " + call.method); 1139 | } 1140 | }); 1141 | 1142 | })(); 1143 | -------------------------------------------------------------------------------- /libs/datatables-css/datatables-crosstalk.css: -------------------------------------------------------------------------------- 1 | .dt-crosstalk-fade { 2 | opacity: 0.2; 3 | } 4 | 5 | html body div.DTS div.dataTables_scrollBody { 6 | background: none; 7 | } 8 | -------------------------------------------------------------------------------- /libs/dt-core/css/jquery.dataTables.extra.css: -------------------------------------------------------------------------------- 1 | /* Selected rows/cells */ 2 | table.dataTable tr.selected td, table.dataTable td.selected { 3 | background-color: #b0bed9 !important; 4 | } 5 | /* In case of scrollX/Y or FixedHeader */ 6 | .dataTables_scrollBody .dataTables_sizing { 7 | visibility: hidden; 8 | } 9 | -------------------------------------------------------------------------------- /libs/dt-core/css/jquery.dataTables.min.css: -------------------------------------------------------------------------------- 1 | table.dataTable{width:100%;margin:0 auto;clear:both;border-collapse:separate;border-spacing:0}table.dataTable thead th,table.dataTable tfoot th{font-weight:bold}table.dataTable thead th,table.dataTable thead td{padding:10px 18px;border-bottom:1px solid #111}table.dataTable thead th:active,table.dataTable thead td:active{outline:none}table.dataTable tfoot th,table.dataTable tfoot td{padding:10px 18px 6px 18px;border-top:1px solid #111}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;*cursor:hand;background-repeat:no-repeat;background-position:center right}table.dataTable thead .sorting{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAkElEQVQoz7XQMQ5AQBCF4dWQSJxC5wwax1Cq1e7BAdxD5SL+Tq/QCM1oNiJidwox0355mXnG/DrEtIQ6azioNZQxI0ykPhTQIwhCR+BmBYtlK7kLJYwWCcJA9M4qdrZrd8pPjZWPtOqdRQy320YSV17OatFC4euts6z39GYMKRPCTKY9UnPQ6P+GtMRfGtPnBCiqhAeJPmkqAAAAAElFTkSuQmCC)}table.dataTable thead .sorting_asc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZ0lEQVQ4y2NgGLKgquEuFxBPAGI2ahhWCsS/gDibUoO0gPgxEP8H4ttArEyuQYxAPBdqEAxPBImTY5gjEL9DM+wTENuQahAvEO9DMwiGdwAxOymGJQLxTyD+jgWDxCMZRsEoGAVoAADeemwtPcZI2wAAAABJRU5ErkJggg==)}table.dataTable thead .sorting_desc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZUlEQVQ4y2NgGAWjYBSggaqGu5FA/BOIv2PBIPFEUgxjB+IdQPwfC94HxLykus4GiD+hGfQOiB3J8SojEE9EM2wuSJzcsFMG4ttQgx4DsRalkZENxL+AuJQaMcsGxBOAmGvopk8AVz1sLZgg0bsAAAAASUVORK5CYII=)}table.dataTable thead .sorting_asc_disabled{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAW0lEQVQoz2NgoCm4w3Vnwh02wspK7/y6k01Ikdadx3f+37l9RxmfIsY7c4GKQHDiHUbcyhzvvIMq+3THBpci3jv7oIpAcMcdduzKEu/8vPMdDn/eiWQYBYMKAAC3ykIEuYQJUgAAAABJRU5ErkJggg==)}table.dataTable thead .sorting_desc_disabled{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAWUlEQVQoz2NgGAWDCtyJvPPzznc4/HknEbsy9js77vyHw313eHGZZ3PnE1TRuzuOuK1lvDMRqmzuHUZ87lO+cxuo6PEdLUIeyb7z604pYf+y3Zlwh4u2YQoAc7ZCBHH4jigAAAAASUVORK5CYII=)}table.dataTable tbody tr{background-color:#ffffff}table.dataTable tbody tr.selected{background-color:#B0BED9}table.dataTable tbody th,table.dataTable tbody td{padding:8px 10px}table.dataTable.row-border tbody th,table.dataTable.row-border tbody td,table.dataTable.display tbody th,table.dataTable.display tbody td{border-top:1px solid #ddd}table.dataTable.row-border tbody tr:first-child th,table.dataTable.row-border tbody tr:first-child td,table.dataTable.display tbody tr:first-child th,table.dataTable.display tbody tr:first-child td{border-top:none}table.dataTable.cell-border tbody th,table.dataTable.cell-border tbody td{border-top:1px solid #ddd;border-right:1px solid #ddd}table.dataTable.cell-border tbody tr th:first-child,table.dataTable.cell-border tbody tr td:first-child{border-left:1px solid #ddd}table.dataTable.cell-border tbody tr:first-child th,table.dataTable.cell-border tbody tr:first-child td{border-top:none}table.dataTable.stripe tbody tr.odd,table.dataTable.display tbody tr.odd{background-color:#f9f9f9}table.dataTable.stripe tbody tr.odd.selected,table.dataTable.display tbody tr.odd.selected{background-color:#acbad4}table.dataTable.hover tbody tr:hover,table.dataTable.display tbody tr:hover{background-color:#f6f6f6}table.dataTable.hover tbody tr:hover.selected,table.dataTable.display tbody tr:hover.selected{background-color:#aab7d1}table.dataTable.order-column tbody tr>.sorting_1,table.dataTable.order-column tbody tr>.sorting_2,table.dataTable.order-column tbody tr>.sorting_3,table.dataTable.display tbody tr>.sorting_1,table.dataTable.display tbody tr>.sorting_2,table.dataTable.display tbody tr>.sorting_3{background-color:#fafafa}table.dataTable.order-column tbody tr.selected>.sorting_1,table.dataTable.order-column tbody tr.selected>.sorting_2,table.dataTable.order-column tbody tr.selected>.sorting_3,table.dataTable.display tbody tr.selected>.sorting_1,table.dataTable.display tbody tr.selected>.sorting_2,table.dataTable.display tbody tr.selected>.sorting_3{background-color:#acbad5}table.dataTable.display tbody tr.odd>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd>.sorting_1{background-color:#f1f1f1}table.dataTable.display tbody tr.odd>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd>.sorting_2{background-color:#f3f3f3}table.dataTable.display tbody tr.odd>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd>.sorting_3{background-color:whitesmoke}table.dataTable.display tbody tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_1{background-color:#a6b4cd}table.dataTable.display tbody tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_2{background-color:#a8b5cf}table.dataTable.display tbody tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_3{background-color:#a9b7d1}table.dataTable.display tbody tr.even>.sorting_1,table.dataTable.order-column.stripe tbody tr.even>.sorting_1{background-color:#fafafa}table.dataTable.display tbody tr.even>.sorting_2,table.dataTable.order-column.stripe tbody tr.even>.sorting_2{background-color:#fcfcfc}table.dataTable.display tbody tr.even>.sorting_3,table.dataTable.order-column.stripe tbody tr.even>.sorting_3{background-color:#fefefe}table.dataTable.display tbody tr.even.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_1{background-color:#acbad5}table.dataTable.display tbody tr.even.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_2{background-color:#aebcd6}table.dataTable.display tbody tr.even.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_3{background-color:#afbdd8}table.dataTable.display tbody tr:hover>.sorting_1,table.dataTable.order-column.hover tbody tr:hover>.sorting_1{background-color:#eaeaea}table.dataTable.display tbody tr:hover>.sorting_2,table.dataTable.order-column.hover tbody tr:hover>.sorting_2{background-color:#ececec}table.dataTable.display tbody tr:hover>.sorting_3,table.dataTable.order-column.hover tbody tr:hover>.sorting_3{background-color:#efefef}table.dataTable.display tbody tr:hover.selected>.sorting_1,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_1{background-color:#a2aec7}table.dataTable.display tbody tr:hover.selected>.sorting_2,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_2{background-color:#a3b0c9}table.dataTable.display tbody tr:hover.selected>.sorting_3,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_3{background-color:#a5b2cb}table.dataTable.no-footer{border-bottom:1px solid #111}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable.compact thead th,table.dataTable.compact thead td{padding:4px 17px 4px 4px}table.dataTable.compact tfoot th,table.dataTable.compact tfoot td{padding:4px}table.dataTable.compact tbody th,table.dataTable.compact tbody td{padding:4px}table.dataTable th.dt-left,table.dataTable td.dt-left{text-align:left}table.dataTable th.dt-center,table.dataTable td.dt-center,table.dataTable td.dataTables_empty{text-align:center}table.dataTable th.dt-right,table.dataTable td.dt-right{text-align:right}table.dataTable th.dt-justify,table.dataTable td.dt-justify{text-align:justify}table.dataTable th.dt-nowrap,table.dataTable td.dt-nowrap{white-space:nowrap}table.dataTable thead th.dt-head-left,table.dataTable thead td.dt-head-left,table.dataTable tfoot th.dt-head-left,table.dataTable tfoot td.dt-head-left{text-align:left}table.dataTable thead th.dt-head-center,table.dataTable thead td.dt-head-center,table.dataTable tfoot th.dt-head-center,table.dataTable tfoot td.dt-head-center{text-align:center}table.dataTable thead th.dt-head-right,table.dataTable thead td.dt-head-right,table.dataTable tfoot th.dt-head-right,table.dataTable tfoot td.dt-head-right{text-align:right}table.dataTable thead th.dt-head-justify,table.dataTable thead td.dt-head-justify,table.dataTable tfoot th.dt-head-justify,table.dataTable tfoot td.dt-head-justify{text-align:justify}table.dataTable thead th.dt-head-nowrap,table.dataTable thead td.dt-head-nowrap,table.dataTable tfoot th.dt-head-nowrap,table.dataTable tfoot td.dt-head-nowrap{white-space:nowrap}table.dataTable tbody th.dt-body-left,table.dataTable tbody td.dt-body-left{text-align:left}table.dataTable tbody th.dt-body-center,table.dataTable tbody td.dt-body-center{text-align:center}table.dataTable tbody th.dt-body-right,table.dataTable tbody td.dt-body-right{text-align:right}table.dataTable tbody th.dt-body-justify,table.dataTable tbody td.dt-body-justify{text-align:justify}table.dataTable tbody th.dt-body-nowrap,table.dataTable tbody td.dt-body-nowrap{white-space:nowrap}table.dataTable,table.dataTable th,table.dataTable td{box-sizing:content-box}.dataTables_wrapper{position:relative;clear:both;*zoom:1;zoom:1}.dataTables_wrapper .dataTables_length{float:left}.dataTables_wrapper .dataTables_filter{float:right;text-align:right}.dataTables_wrapper .dataTables_filter input{margin-left:0.5em}.dataTables_wrapper .dataTables_info{clear:both;float:left;padding-top:0.755em}.dataTables_wrapper .dataTables_paginate{float:right;text-align:right;padding-top:0.25em}.dataTables_wrapper .dataTables_paginate .paginate_button{box-sizing:border-box;display:inline-block;min-width:1.5em;padding:0.5em 1em;margin-left:2px;text-align:center;text-decoration:none !important;cursor:pointer;*cursor:hand;color:#333 !important;border:1px solid transparent;border-radius:2px}.dataTables_wrapper .dataTables_paginate .paginate_button.current,.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover{color:#333 !important;border:1px solid #979797;background-color:white;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fff), color-stop(100%, #dcdcdc));background:-webkit-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-moz-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-ms-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-o-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:linear-gradient(to bottom, #fff 0%, #dcdcdc 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active{cursor:default;color:#666 !important;border:1px solid transparent;background:transparent;box-shadow:none}.dataTables_wrapper .dataTables_paginate .paginate_button:hover{color:white !important;border:1px solid #111;background-color:#585858;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));background:-webkit-linear-gradient(top, #585858 0%, #111 100%);background:-moz-linear-gradient(top, #585858 0%, #111 100%);background:-ms-linear-gradient(top, #585858 0%, #111 100%);background:-o-linear-gradient(top, #585858 0%, #111 100%);background:linear-gradient(to bottom, #585858 0%, #111 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button:active{outline:none;background-color:#2b2b2b;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));background:-webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);box-shadow:inset 0 0 3px #111}.dataTables_wrapper .dataTables_paginate .ellipsis{padding:0 1em}.dataTables_wrapper .dataTables_processing{position:absolute;top:50%;left:50%;width:100%;height:40px;margin-left:-50%;margin-top:-25px;padding-top:20px;text-align:center;font-size:1.2em;background-color:white;background:-webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255,255,255,0)), color-stop(25%, rgba(255,255,255,0.9)), color-stop(75%, rgba(255,255,255,0.9)), color-stop(100%, rgba(255,255,255,0)));background:-webkit-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-moz-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-ms-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-o-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%)}.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter,.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_processing,.dataTables_wrapper .dataTables_paginate{color:#333}.dataTables_wrapper .dataTables_scroll{clear:both}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody{*margin-top:-1px;-webkit-overflow-scrolling:touch}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td{vertical-align:middle}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td>div.dataTables_sizing{height:0;overflow:hidden;margin:0 !important;padding:0 !important}.dataTables_wrapper.no-footer .dataTables_scrollBody{border-bottom:1px solid #111}.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable,.dataTables_wrapper.no-footer div.dataTables_scrollBody>table{border-bottom:none}.dataTables_wrapper:after{visibility:hidden;display:block;content:"";clear:both;height:0}@media screen and (max-width: 767px){.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_paginate{float:none;text-align:center}.dataTables_wrapper .dataTables_paginate{margin-top:0.5em}}@media screen and (max-width: 640px){.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter{float:none;text-align:center}.dataTables_wrapper .dataTables_filter{margin-top:0.5em}} 2 | -------------------------------------------------------------------------------- /libs/htmlwidgets/htmlwidgets.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | // If window.HTMLWidgets is already defined, then use it; otherwise create a 3 | // new object. This allows preceding code to set options that affect the 4 | // initialization process (though none currently exist). 5 | window.HTMLWidgets = window.HTMLWidgets || {}; 6 | 7 | // See if we're running in a viewer pane. If not, we're in a web browser. 8 | var viewerMode = window.HTMLWidgets.viewerMode = 9 | /\bviewer_pane=1\b/.test(window.location); 10 | 11 | // See if we're running in Shiny mode. If not, it's a static document. 12 | // Note that static widgets can appear in both Shiny and static modes, but 13 | // obviously, Shiny widgets can only appear in Shiny apps/documents. 14 | var shinyMode = window.HTMLWidgets.shinyMode = 15 | typeof(window.Shiny) !== "undefined" && !!window.Shiny.outputBindings; 16 | 17 | // We can't count on jQuery being available, so we implement our own 18 | // version if necessary. 19 | function querySelectorAll(scope, selector) { 20 | if (typeof(jQuery) !== "undefined" && scope instanceof jQuery) { 21 | return scope.find(selector); 22 | } 23 | if (scope.querySelectorAll) { 24 | return scope.querySelectorAll(selector); 25 | } 26 | } 27 | 28 | function asArray(value) { 29 | if (value === null) 30 | return []; 31 | if ($.isArray(value)) 32 | return value; 33 | return [value]; 34 | } 35 | 36 | // Implement jQuery's extend 37 | function extend(target /*, ... */) { 38 | if (arguments.length == 1) { 39 | return target; 40 | } 41 | for (var i = 1; i < arguments.length; i++) { 42 | var source = arguments[i]; 43 | for (var prop in source) { 44 | if (source.hasOwnProperty(prop)) { 45 | target[prop] = source[prop]; 46 | } 47 | } 48 | } 49 | return target; 50 | } 51 | 52 | // IE8 doesn't support Array.forEach. 53 | function forEach(values, callback, thisArg) { 54 | if (values.forEach) { 55 | values.forEach(callback, thisArg); 56 | } else { 57 | for (var i = 0; i < values.length; i++) { 58 | callback.call(thisArg, values[i], i, values); 59 | } 60 | } 61 | } 62 | 63 | // Replaces the specified method with the return value of funcSource. 64 | // 65 | // Note that funcSource should not BE the new method, it should be a function 66 | // that RETURNS the new method. funcSource receives a single argument that is 67 | // the overridden method, it can be called from the new method. The overridden 68 | // method can be called like a regular function, it has the target permanently 69 | // bound to it so "this" will work correctly. 70 | function overrideMethod(target, methodName, funcSource) { 71 | var superFunc = target[methodName] || function() {}; 72 | var superFuncBound = function() { 73 | return superFunc.apply(target, arguments); 74 | }; 75 | target[methodName] = funcSource(superFuncBound); 76 | } 77 | 78 | // Add a method to delegator that, when invoked, calls 79 | // delegatee.methodName. If there is no such method on 80 | // the delegatee, but there was one on delegator before 81 | // delegateMethod was called, then the original version 82 | // is invoked instead. 83 | // For example: 84 | // 85 | // var a = { 86 | // method1: function() { console.log('a1'); } 87 | // method2: function() { console.log('a2'); } 88 | // }; 89 | // var b = { 90 | // method1: function() { console.log('b1'); } 91 | // }; 92 | // delegateMethod(a, b, "method1"); 93 | // delegateMethod(a, b, "method2"); 94 | // a.method1(); 95 | // a.method2(); 96 | // 97 | // The output would be "b1", "a2". 98 | function delegateMethod(delegator, delegatee, methodName) { 99 | var inherited = delegator[methodName]; 100 | delegator[methodName] = function() { 101 | var target = delegatee; 102 | var method = delegatee[methodName]; 103 | 104 | // The method doesn't exist on the delegatee. Instead, 105 | // call the method on the delegator, if it exists. 106 | if (!method) { 107 | target = delegator; 108 | method = inherited; 109 | } 110 | 111 | if (method) { 112 | return method.apply(target, arguments); 113 | } 114 | }; 115 | } 116 | 117 | // Implement a vague facsimilie of jQuery's data method 118 | function elementData(el, name, value) { 119 | if (arguments.length == 2) { 120 | return el["htmlwidget_data_" + name]; 121 | } else if (arguments.length == 3) { 122 | el["htmlwidget_data_" + name] = value; 123 | return el; 124 | } else { 125 | throw new Error("Wrong number of arguments for elementData: " + 126 | arguments.length); 127 | } 128 | } 129 | 130 | // http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex 131 | function escapeRegExp(str) { 132 | return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); 133 | } 134 | 135 | function hasClass(el, className) { 136 | var re = new RegExp("\\b" + escapeRegExp(className) + "\\b"); 137 | return re.test(el.className); 138 | } 139 | 140 | // elements - array (or array-like object) of HTML elements 141 | // className - class name to test for 142 | // include - if true, only return elements with given className; 143 | // if false, only return elements *without* given className 144 | function filterByClass(elements, className, include) { 145 | var results = []; 146 | for (var i = 0; i < elements.length; i++) { 147 | if (hasClass(elements[i], className) == include) 148 | results.push(elements[i]); 149 | } 150 | return results; 151 | } 152 | 153 | function on(obj, eventName, func) { 154 | if (obj.addEventListener) { 155 | obj.addEventListener(eventName, func, false); 156 | } else if (obj.attachEvent) { 157 | obj.attachEvent(eventName, func); 158 | } 159 | } 160 | 161 | function off(obj, eventName, func) { 162 | if (obj.removeEventListener) 163 | obj.removeEventListener(eventName, func, false); 164 | else if (obj.detachEvent) { 165 | obj.detachEvent(eventName, func); 166 | } 167 | } 168 | 169 | // Translate array of values to top/right/bottom/left, as usual with 170 | // the "padding" CSS property 171 | // https://developer.mozilla.org/en-US/docs/Web/CSS/padding 172 | function unpackPadding(value) { 173 | if (typeof(value) === "number") 174 | value = [value]; 175 | if (value.length === 1) { 176 | return {top: value[0], right: value[0], bottom: value[0], left: value[0]}; 177 | } 178 | if (value.length === 2) { 179 | return {top: value[0], right: value[1], bottom: value[0], left: value[1]}; 180 | } 181 | if (value.length === 3) { 182 | return {top: value[0], right: value[1], bottom: value[2], left: value[1]}; 183 | } 184 | if (value.length === 4) { 185 | return {top: value[0], right: value[1], bottom: value[2], left: value[3]}; 186 | } 187 | } 188 | 189 | // Convert an unpacked padding object to a CSS value 190 | function paddingToCss(paddingObj) { 191 | return paddingObj.top + "px " + paddingObj.right + "px " + paddingObj.bottom + "px " + paddingObj.left + "px"; 192 | } 193 | 194 | // Makes a number suitable for CSS 195 | function px(x) { 196 | if (typeof(x) === "number") 197 | return x + "px"; 198 | else 199 | return x; 200 | } 201 | 202 | // Retrieves runtime widget sizing information for an element. 203 | // The return value is either null, or an object with fill, padding, 204 | // defaultWidth, defaultHeight fields. 205 | function sizingPolicy(el) { 206 | var sizingEl = document.querySelector("script[data-for='" + el.id + "'][type='application/htmlwidget-sizing']"); 207 | if (!sizingEl) 208 | return null; 209 | var sp = JSON.parse(sizingEl.textContent || sizingEl.text || "{}"); 210 | if (viewerMode) { 211 | return sp.viewer; 212 | } else { 213 | return sp.browser; 214 | } 215 | } 216 | 217 | // @param tasks Array of strings (or falsy value, in which case no-op). 218 | // Each element must be a valid JavaScript expression that yields a 219 | // function. Or, can be an array of objects with "code" and "data" 220 | // properties; in this case, the "code" property should be a string 221 | // of JS that's an expr that yields a function, and "data" should be 222 | // an object that will be added as an additional argument when that 223 | // function is called. 224 | // @param target The object that will be "this" for each function 225 | // execution. 226 | // @param args Array of arguments to be passed to the functions. (The 227 | // same arguments will be passed to all functions.) 228 | function evalAndRun(tasks, target, args) { 229 | if (tasks) { 230 | forEach(tasks, function(task) { 231 | var theseArgs = args; 232 | if (typeof(task) === "object") { 233 | theseArgs = theseArgs.concat([task.data]); 234 | task = task.code; 235 | } 236 | var taskFunc = eval("(" + task + ")"); 237 | if (typeof(taskFunc) !== "function") { 238 | throw new Error("Task must be a function! Source:\n" + task); 239 | } 240 | taskFunc.apply(target, theseArgs); 241 | }); 242 | } 243 | } 244 | 245 | function initSizing(el) { 246 | var sizing = sizingPolicy(el); 247 | if (!sizing) 248 | return; 249 | 250 | var cel = document.getElementById("htmlwidget_container"); 251 | if (!cel) 252 | return; 253 | 254 | if (typeof(sizing.padding) !== "undefined") { 255 | document.body.style.margin = "0"; 256 | document.body.style.padding = paddingToCss(unpackPadding(sizing.padding)); 257 | } 258 | 259 | if (sizing.fill) { 260 | document.body.style.overflow = "hidden"; 261 | document.body.style.width = "100%"; 262 | document.body.style.height = "100%"; 263 | document.documentElement.style.width = "100%"; 264 | document.documentElement.style.height = "100%"; 265 | if (cel) { 266 | cel.style.position = "absolute"; 267 | var pad = unpackPadding(sizing.padding); 268 | cel.style.top = pad.top + "px"; 269 | cel.style.right = pad.right + "px"; 270 | cel.style.bottom = pad.bottom + "px"; 271 | cel.style.left = pad.left + "px"; 272 | el.style.width = "100%"; 273 | el.style.height = "100%"; 274 | } 275 | 276 | return { 277 | getWidth: function() { return cel.offsetWidth; }, 278 | getHeight: function() { return cel.offsetHeight; } 279 | }; 280 | 281 | } else { 282 | el.style.width = px(sizing.width); 283 | el.style.height = px(sizing.height); 284 | 285 | return { 286 | getWidth: function() { return el.offsetWidth; }, 287 | getHeight: function() { return el.offsetHeight; } 288 | }; 289 | } 290 | } 291 | 292 | // Default implementations for methods 293 | var defaults = { 294 | find: function(scope) { 295 | return querySelectorAll(scope, "." + this.name); 296 | }, 297 | renderError: function(el, err) { 298 | var $el = $(el); 299 | 300 | this.clearError(el); 301 | 302 | // Add all these error classes, as Shiny does 303 | var errClass = "shiny-output-error"; 304 | if (err.type !== null) { 305 | // use the classes of the error condition as CSS class names 306 | errClass = errClass + " " + $.map(asArray(err.type), function(type) { 307 | return errClass + "-" + type; 308 | }).join(" "); 309 | } 310 | errClass = errClass + " htmlwidgets-error"; 311 | 312 | // Is el inline or block? If inline or inline-block, just display:none it 313 | // and add an inline error. 314 | var display = $el.css("display"); 315 | $el.data("restore-display-mode", display); 316 | 317 | if (display === "inline" || display === "inline-block") { 318 | $el.hide(); 319 | if (err.message !== "") { 320 | var errorSpan = $("").addClass(errClass); 321 | errorSpan.text(err.message); 322 | $el.after(errorSpan); 323 | } 324 | } else if (display === "block") { 325 | // If block, add an error just after the el, set visibility:none on the 326 | // el, and position the error to be on top of the el. 327 | // Mark it with a unique ID and CSS class so we can remove it later. 328 | $el.css("visibility", "hidden"); 329 | if (err.message !== "") { 330 | var errorDiv = $("
").addClass(errClass).css("position", "absolute") 331 | .css("top", el.offsetTop) 332 | .css("left", el.offsetLeft) 333 | // setting width can push out the page size, forcing otherwise 334 | // unnecessary scrollbars to appear and making it impossible for 335 | // the element to shrink; so use max-width instead 336 | .css("maxWidth", el.offsetWidth) 337 | .css("height", el.offsetHeight); 338 | errorDiv.text(err.message); 339 | $el.after(errorDiv); 340 | 341 | // Really dumb way to keep the size/position of the error in sync with 342 | // the parent element as the window is resized or whatever. 343 | var intId = setInterval(function() { 344 | if (!errorDiv[0].parentElement) { 345 | clearInterval(intId); 346 | return; 347 | } 348 | errorDiv 349 | .css("top", el.offsetTop) 350 | .css("left", el.offsetLeft) 351 | .css("maxWidth", el.offsetWidth) 352 | .css("height", el.offsetHeight); 353 | }, 500); 354 | } 355 | } 356 | }, 357 | clearError: function(el) { 358 | var $el = $(el); 359 | var display = $el.data("restore-display-mode"); 360 | $el.data("restore-display-mode", null); 361 | 362 | if (display === "inline" || display === "inline-block") { 363 | if (display) 364 | $el.css("display", display); 365 | $(el.nextSibling).filter(".htmlwidgets-error").remove(); 366 | } else if (display === "block"){ 367 | $el.css("visibility", "inherit"); 368 | $(el.nextSibling).filter(".htmlwidgets-error").remove(); 369 | } 370 | }, 371 | sizing: {} 372 | }; 373 | 374 | // Called by widget bindings to register a new type of widget. The definition 375 | // object can contain the following properties: 376 | // - name (required) - A string indicating the binding name, which will be 377 | // used by default as the CSS classname to look for. 378 | // - initialize (optional) - A function(el) that will be called once per 379 | // widget element; if a value is returned, it will be passed as the third 380 | // value to renderValue. 381 | // - renderValue (required) - A function(el, data, initValue) that will be 382 | // called with data. Static contexts will cause this to be called once per 383 | // element; Shiny apps will cause this to be called multiple times per 384 | // element, as the data changes. 385 | window.HTMLWidgets.widget = function(definition) { 386 | if (!definition.name) { 387 | throw new Error("Widget must have a name"); 388 | } 389 | if (!definition.type) { 390 | throw new Error("Widget must have a type"); 391 | } 392 | // Currently we only support output widgets 393 | if (definition.type !== "output") { 394 | throw new Error("Unrecognized widget type '" + definition.type + "'"); 395 | } 396 | // TODO: Verify that .name is a valid CSS classname 397 | 398 | // Support new-style instance-bound definitions. Old-style class-bound 399 | // definitions have one widget "object" per widget per type/class of 400 | // widget; the renderValue and resize methods on such widget objects 401 | // take el and instance arguments, because the widget object can't 402 | // store them. New-style instance-bound definitions have one widget 403 | // object per widget instance; the definition that's passed in doesn't 404 | // provide renderValue or resize methods at all, just the single method 405 | // factory(el, width, height) 406 | // which returns an object that has renderValue(x) and resize(w, h). 407 | // This enables a far more natural programming style for the widget 408 | // author, who can store per-instance state using either OO-style 409 | // instance fields or functional-style closure variables (I guess this 410 | // is in contrast to what can only be called C-style pseudo-OO which is 411 | // what we required before). 412 | if (definition.factory) { 413 | definition = createLegacyDefinitionAdapter(definition); 414 | } 415 | 416 | if (!definition.renderValue) { 417 | throw new Error("Widget must have a renderValue function"); 418 | } 419 | 420 | // For static rendering (non-Shiny), use a simple widget registration 421 | // scheme. We also use this scheme for Shiny apps/documents that also 422 | // contain static widgets. 423 | window.HTMLWidgets.widgets = window.HTMLWidgets.widgets || []; 424 | // Merge defaults into the definition; don't mutate the original definition. 425 | var staticBinding = extend({}, defaults, definition); 426 | overrideMethod(staticBinding, "find", function(superfunc) { 427 | return function(scope) { 428 | var results = superfunc(scope); 429 | // Filter out Shiny outputs, we only want the static kind 430 | return filterByClass(results, "html-widget-output", false); 431 | }; 432 | }); 433 | window.HTMLWidgets.widgets.push(staticBinding); 434 | 435 | if (shinyMode) { 436 | // Shiny is running. Register the definition with an output binding. 437 | // The definition itself will not be the output binding, instead 438 | // we will make an output binding object that delegates to the 439 | // definition. This is because we foolishly used the same method 440 | // name (renderValue) for htmlwidgets definition and Shiny bindings 441 | // but they actually have quite different semantics (the Shiny 442 | // bindings receive data that includes lots of metadata that it 443 | // strips off before calling htmlwidgets renderValue). We can't 444 | // just ignore the difference because in some widgets it's helpful 445 | // to call this.renderValue() from inside of resize(), and if 446 | // we're not delegating, then that call will go to the Shiny 447 | // version instead of the htmlwidgets version. 448 | 449 | // Merge defaults with definition, without mutating either. 450 | var bindingDef = extend({}, defaults, definition); 451 | 452 | // This object will be our actual Shiny binding. 453 | var shinyBinding = new Shiny.OutputBinding(); 454 | 455 | // With a few exceptions, we'll want to simply use the bindingDef's 456 | // version of methods if they are available, otherwise fall back to 457 | // Shiny's defaults. NOTE: If Shiny's output bindings gain additional 458 | // methods in the future, and we want them to be overrideable by 459 | // HTMLWidget binding definitions, then we'll need to add them to this 460 | // list. 461 | delegateMethod(shinyBinding, bindingDef, "getId"); 462 | delegateMethod(shinyBinding, bindingDef, "onValueChange"); 463 | delegateMethod(shinyBinding, bindingDef, "onValueError"); 464 | delegateMethod(shinyBinding, bindingDef, "renderError"); 465 | delegateMethod(shinyBinding, bindingDef, "clearError"); 466 | delegateMethod(shinyBinding, bindingDef, "showProgress"); 467 | 468 | // The find, renderValue, and resize are handled differently, because we 469 | // want to actually decorate the behavior of the bindingDef methods. 470 | 471 | shinyBinding.find = function(scope) { 472 | var results = bindingDef.find(scope); 473 | 474 | // Only return elements that are Shiny outputs, not static ones 475 | var dynamicResults = results.filter(".html-widget-output"); 476 | 477 | // It's possible that whatever caused Shiny to think there might be 478 | // new dynamic outputs, also caused there to be new static outputs. 479 | // Since there might be lots of different htmlwidgets bindings, we 480 | // schedule execution for later--no need to staticRender multiple 481 | // times. 482 | if (results.length !== dynamicResults.length) 483 | scheduleStaticRender(); 484 | 485 | return dynamicResults; 486 | }; 487 | 488 | // Wrap renderValue to handle initialization, which unfortunately isn't 489 | // supported natively by Shiny at the time of this writing. 490 | 491 | shinyBinding.renderValue = function(el, data) { 492 | Shiny.renderDependencies(data.deps); 493 | // Resolve strings marked as javascript literals to objects 494 | if (!(data.evals instanceof Array)) data.evals = [data.evals]; 495 | for (var i = 0; data.evals && i < data.evals.length; i++) { 496 | window.HTMLWidgets.evaluateStringMember(data.x, data.evals[i]); 497 | } 498 | if (!bindingDef.renderOnNullValue) { 499 | if (data.x === null) { 500 | el.style.visibility = "hidden"; 501 | return; 502 | } else { 503 | el.style.visibility = "inherit"; 504 | } 505 | } 506 | if (!elementData(el, "initialized")) { 507 | initSizing(el); 508 | 509 | elementData(el, "initialized", true); 510 | if (bindingDef.initialize) { 511 | var result = bindingDef.initialize(el, el.offsetWidth, 512 | el.offsetHeight); 513 | elementData(el, "init_result", result); 514 | } 515 | } 516 | bindingDef.renderValue(el, data.x, elementData(el, "init_result")); 517 | evalAndRun(data.jsHooks.render, elementData(el, "init_result"), [el, data.x]); 518 | }; 519 | 520 | // Only override resize if bindingDef implements it 521 | if (bindingDef.resize) { 522 | shinyBinding.resize = function(el, width, height) { 523 | // Shiny can call resize before initialize/renderValue have been 524 | // called, which doesn't make sense for widgets. 525 | if (elementData(el, "initialized")) { 526 | bindingDef.resize(el, width, height, elementData(el, "init_result")); 527 | } 528 | }; 529 | } 530 | 531 | Shiny.outputBindings.register(shinyBinding, bindingDef.name); 532 | } 533 | }; 534 | 535 | var scheduleStaticRenderTimerId = null; 536 | function scheduleStaticRender() { 537 | if (!scheduleStaticRenderTimerId) { 538 | scheduleStaticRenderTimerId = setTimeout(function() { 539 | scheduleStaticRenderTimerId = null; 540 | window.HTMLWidgets.staticRender(); 541 | }, 1); 542 | } 543 | } 544 | 545 | // Render static widgets after the document finishes loading 546 | // Statically render all elements that are of this widget's class 547 | window.HTMLWidgets.staticRender = function() { 548 | var bindings = window.HTMLWidgets.widgets || []; 549 | forEach(bindings, function(binding) { 550 | var matches = binding.find(document.documentElement); 551 | forEach(matches, function(el) { 552 | var sizeObj = initSizing(el, binding); 553 | 554 | if (hasClass(el, "html-widget-static-bound")) 555 | return; 556 | el.className = el.className + " html-widget-static-bound"; 557 | 558 | var initResult; 559 | if (binding.initialize) { 560 | initResult = binding.initialize(el, 561 | sizeObj ? sizeObj.getWidth() : el.offsetWidth, 562 | sizeObj ? sizeObj.getHeight() : el.offsetHeight 563 | ); 564 | elementData(el, "init_result", initResult); 565 | } 566 | 567 | if (binding.resize) { 568 | var lastSize = { 569 | w: sizeObj ? sizeObj.getWidth() : el.offsetWidth, 570 | h: sizeObj ? sizeObj.getHeight() : el.offsetHeight 571 | }; 572 | var resizeHandler = function(e) { 573 | var size = { 574 | w: sizeObj ? sizeObj.getWidth() : el.offsetWidth, 575 | h: sizeObj ? sizeObj.getHeight() : el.offsetHeight 576 | }; 577 | if (size.w === 0 && size.h === 0) 578 | return; 579 | if (size.w === lastSize.w && size.h === lastSize.h) 580 | return; 581 | lastSize = size; 582 | binding.resize(el, size.w, size.h, initResult); 583 | }; 584 | 585 | on(window, "resize", resizeHandler); 586 | 587 | // This is needed for cases where we're running in a Shiny 588 | // app, but the widget itself is not a Shiny output, but 589 | // rather a simple static widget. One example of this is 590 | // an rmarkdown document that has runtime:shiny and widget 591 | // that isn't in a render function. Shiny only knows to 592 | // call resize handlers for Shiny outputs, not for static 593 | // widgets, so we do it ourselves. 594 | if (window.jQuery) { 595 | window.jQuery(document).on( 596 | "shown.htmlwidgets shown.bs.tab.htmlwidgets shown.bs.collapse.htmlwidgets", 597 | resizeHandler 598 | ); 599 | window.jQuery(document).on( 600 | "hidden.htmlwidgets hidden.bs.tab.htmlwidgets hidden.bs.collapse.htmlwidgets", 601 | resizeHandler 602 | ); 603 | } 604 | 605 | // This is needed for the specific case of ioslides, which 606 | // flips slides between display:none and display:block. 607 | // Ideally we would not have to have ioslide-specific code 608 | // here, but rather have ioslides raise a generic event, 609 | // but the rmarkdown package just went to CRAN so the 610 | // window to getting that fixed may be long. 611 | if (window.addEventListener) { 612 | // It's OK to limit this to window.addEventListener 613 | // browsers because ioslides itself only supports 614 | // such browsers. 615 | on(document, "slideenter", resizeHandler); 616 | on(document, "slideleave", resizeHandler); 617 | } 618 | } 619 | 620 | var scriptData = document.querySelector("script[data-for='" + el.id + "'][type='application/json']"); 621 | if (scriptData) { 622 | var data = JSON.parse(scriptData.textContent || scriptData.text); 623 | // Resolve strings marked as javascript literals to objects 624 | if (!(data.evals instanceof Array)) data.evals = [data.evals]; 625 | for (var k = 0; data.evals && k < data.evals.length; k++) { 626 | window.HTMLWidgets.evaluateStringMember(data.x, data.evals[k]); 627 | } 628 | binding.renderValue(el, data.x, initResult); 629 | evalAndRun(data.jsHooks.render, initResult, [el, data.x]); 630 | } 631 | }); 632 | }); 633 | 634 | invokePostRenderHandlers(); 635 | } 636 | 637 | // Wait until after the document has loaded to render the widgets. 638 | if (document.addEventListener) { 639 | document.addEventListener("DOMContentLoaded", function() { 640 | document.removeEventListener("DOMContentLoaded", arguments.callee, false); 641 | window.HTMLWidgets.staticRender(); 642 | }, false); 643 | } else if (document.attachEvent) { 644 | document.attachEvent("onreadystatechange", function() { 645 | if (document.readyState === "complete") { 646 | document.detachEvent("onreadystatechange", arguments.callee); 647 | window.HTMLWidgets.staticRender(); 648 | } 649 | }); 650 | } 651 | 652 | 653 | window.HTMLWidgets.getAttachmentUrl = function(depname, key) { 654 | // If no key, default to the first item 655 | if (typeof(key) === "undefined") 656 | key = 1; 657 | 658 | var link = document.getElementById(depname + "-" + key + "-attachment"); 659 | if (!link) { 660 | throw new Error("Attachment " + depname + "/" + key + " not found in document"); 661 | } 662 | return link.getAttribute("href"); 663 | }; 664 | 665 | window.HTMLWidgets.dataframeToD3 = function(df) { 666 | var names = []; 667 | var length; 668 | for (var name in df) { 669 | if (df.hasOwnProperty(name)) 670 | names.push(name); 671 | if (typeof(df[name]) !== "object" || typeof(df[name].length) === "undefined") { 672 | throw new Error("All fields must be arrays"); 673 | } else if (typeof(length) !== "undefined" && length !== df[name].length) { 674 | throw new Error("All fields must be arrays of the same length"); 675 | } 676 | length = df[name].length; 677 | } 678 | var results = []; 679 | var item; 680 | for (var row = 0; row < length; row++) { 681 | item = {}; 682 | for (var col = 0; col < names.length; col++) { 683 | item[names[col]] = df[names[col]][row]; 684 | } 685 | results.push(item); 686 | } 687 | return results; 688 | }; 689 | 690 | window.HTMLWidgets.transposeArray2D = function(array) { 691 | if (array.length === 0) return array; 692 | var newArray = array[0].map(function(col, i) { 693 | return array.map(function(row) { 694 | return row[i] 695 | }) 696 | }); 697 | return newArray; 698 | }; 699 | // Split value at splitChar, but allow splitChar to be escaped 700 | // using escapeChar. Any other characters escaped by escapeChar 701 | // will be included as usual (including escapeChar itself). 702 | function splitWithEscape(value, splitChar, escapeChar) { 703 | var results = []; 704 | var escapeMode = false; 705 | var currentResult = ""; 706 | for (var pos = 0; pos < value.length; pos++) { 707 | if (!escapeMode) { 708 | if (value[pos] === splitChar) { 709 | results.push(currentResult); 710 | currentResult = ""; 711 | } else if (value[pos] === escapeChar) { 712 | escapeMode = true; 713 | } else { 714 | currentResult += value[pos]; 715 | } 716 | } else { 717 | currentResult += value[pos]; 718 | escapeMode = false; 719 | } 720 | } 721 | if (currentResult !== "") { 722 | results.push(currentResult); 723 | } 724 | return results; 725 | } 726 | // Function authored by Yihui/JJ Allaire 727 | window.HTMLWidgets.evaluateStringMember = function(o, member) { 728 | var parts = splitWithEscape(member, '.', '\\'); 729 | for (var i = 0, l = parts.length; i < l; i++) { 730 | var part = parts[i]; 731 | // part may be a character or 'numeric' member name 732 | if (o !== null && typeof o === "object" && part in o) { 733 | if (i == (l - 1)) { // if we are at the end of the line then evalulate 734 | if (typeof o[part] === "string") 735 | o[part] = eval("(" + o[part] + ")"); 736 | } else { // otherwise continue to next embedded object 737 | o = o[part]; 738 | } 739 | } 740 | } 741 | }; 742 | 743 | // Retrieve the HTMLWidget instance (i.e. the return value of an 744 | // HTMLWidget binding's initialize() or factory() function) 745 | // associated with an element, or null if none. 746 | window.HTMLWidgets.getInstance = function(el) { 747 | return elementData(el, "init_result"); 748 | }; 749 | 750 | // Finds the first element in the scope that matches the selector, 751 | // and returns the HTMLWidget instance (i.e. the return value of 752 | // an HTMLWidget binding's initialize() or factory() function) 753 | // associated with that element, if any. If no element matches the 754 | // selector, or the first matching element has no HTMLWidget 755 | // instance associated with it, then null is returned. 756 | // 757 | // The scope argument is optional, and defaults to window.document. 758 | window.HTMLWidgets.find = function(scope, selector) { 759 | if (arguments.length == 1) { 760 | selector = scope; 761 | scope = document; 762 | } 763 | 764 | var el = scope.querySelector(selector); 765 | if (el === null) { 766 | return null; 767 | } else { 768 | return window.HTMLWidgets.getInstance(el); 769 | } 770 | }; 771 | 772 | // Finds all elements in the scope that match the selector, and 773 | // returns the HTMLWidget instances (i.e. the return values of 774 | // an HTMLWidget binding's initialize() or factory() function) 775 | // associated with the elements, in an array. If elements that 776 | // match the selector don't have an associated HTMLWidget 777 | // instance, the returned array will contain nulls. 778 | // 779 | // The scope argument is optional, and defaults to window.document. 780 | window.HTMLWidgets.findAll = function(scope, selector) { 781 | if (arguments.length == 1) { 782 | selector = scope; 783 | scope = document; 784 | } 785 | 786 | var nodes = scope.querySelectorAll(selector); 787 | var results = []; 788 | for (var i = 0; i < nodes.length; i++) { 789 | results.push(window.HTMLWidgets.getInstance(nodes[i])); 790 | } 791 | return results; 792 | }; 793 | 794 | var postRenderHandlers = []; 795 | function invokePostRenderHandlers() { 796 | while (postRenderHandlers.length) { 797 | var handler = postRenderHandlers.shift(); 798 | if (handler) { 799 | handler(); 800 | } 801 | } 802 | } 803 | 804 | // Register the given callback function to be invoked after the 805 | // next time static widgets are rendered. 806 | window.HTMLWidgets.addPostRenderHandler = function(callback) { 807 | postRenderHandlers.push(callback); 808 | }; 809 | 810 | // Takes a new-style instance-bound definition, and returns an 811 | // old-style class-bound definition. This saves us from having 812 | // to rewrite all the logic in this file to accomodate both 813 | // types of definitions. 814 | function createLegacyDefinitionAdapter(defn) { 815 | var result = { 816 | name: defn.name, 817 | type: defn.type, 818 | initialize: function(el, width, height) { 819 | return defn.factory(el, width, height); 820 | }, 821 | renderValue: function(el, x, instance) { 822 | return instance.renderValue(x); 823 | }, 824 | resize: function(el, width, height, instance) { 825 | return instance.resize(width, height); 826 | } 827 | }; 828 | 829 | if (defn.find) 830 | result.find = defn.find; 831 | if (defn.renderError) 832 | result.renderError = defn.renderError; 833 | if (defn.clearError) 834 | result.clearError = defn.clearError; 835 | 836 | return result; 837 | } 838 | })(); 839 | 840 | -------------------------------------------------------------------------------- /libs/jquery/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2005, 2014 jQuery Foundation and other contributors, 2 | https://jquery.org/ 3 | 4 | This software consists of voluntary contributions made by many 5 | individuals. For exact contribution history, see the revision history 6 | available at https://github.com/jquery/jquery 7 | 8 | The following license applies to all parts of this software except as 9 | documented below: 10 | 11 | ==== 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining 14 | a copy of this software and associated documentation files (the 15 | "Software"), to deal in the Software without restriction, including 16 | without limitation the rights to use, copy, modify, merge, publish, 17 | distribute, sublicense, and/or sell copies of the Software, and to 18 | permit persons to whom the Software is furnished to do so, subject to 19 | the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be 22 | included in all copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 27 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 28 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 29 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 30 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | 32 | ==== 33 | 34 | All files located in the node_modules and external directories are 35 | externally maintained libraries used by this software which have their 36 | own licenses; we recommend you read them, as their terms may differ from 37 | the terms above. 38 | -------------------------------------------------------------------------------- /libs/leaflet/images/layers-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrrs/r_beginners_workshop/1971ded361f43811a542583a43a0e82fd256642e/libs/leaflet/images/layers-2x.png -------------------------------------------------------------------------------- /libs/leaflet/images/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrrs/r_beginners_workshop/1971ded361f43811a542583a43a0e82fd256642e/libs/leaflet/images/layers.png -------------------------------------------------------------------------------- /libs/leaflet/images/marker-icon-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrrs/r_beginners_workshop/1971ded361f43811a542583a43a0e82fd256642e/libs/leaflet/images/marker-icon-2x.png -------------------------------------------------------------------------------- /libs/leaflet/images/marker-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrrs/r_beginners_workshop/1971ded361f43811a542583a43a0e82fd256642e/libs/leaflet/images/marker-icon.png -------------------------------------------------------------------------------- /libs/leaflet/images/marker-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrrs/r_beginners_workshop/1971ded361f43811a542583a43a0e82fd256642e/libs/leaflet/images/marker-shadow.png -------------------------------------------------------------------------------- /libs/leaflet/leaflet.css: -------------------------------------------------------------------------------- 1 | /* required styles */ 2 | 3 | .leaflet-pane, 4 | .leaflet-tile, 5 | .leaflet-marker-icon, 6 | .leaflet-marker-shadow, 7 | .leaflet-tile-container, 8 | .leaflet-pane > svg, 9 | .leaflet-pane > canvas, 10 | .leaflet-zoom-box, 11 | .leaflet-image-layer, 12 | .leaflet-layer { 13 | position: absolute; 14 | left: 0; 15 | top: 0; 16 | } 17 | .leaflet-container { 18 | overflow: hidden; 19 | } 20 | .leaflet-tile, 21 | .leaflet-marker-icon, 22 | .leaflet-marker-shadow { 23 | -webkit-user-select: none; 24 | -moz-user-select: none; 25 | user-select: none; 26 | -webkit-user-drag: none; 27 | } 28 | /* Safari renders non-retina tile on retina better with this, but Chrome is worse */ 29 | .leaflet-safari .leaflet-tile { 30 | image-rendering: -webkit-optimize-contrast; 31 | } 32 | /* hack that prevents hw layers "stretching" when loading new tiles */ 33 | .leaflet-safari .leaflet-tile-container { 34 | width: 1600px; 35 | height: 1600px; 36 | -webkit-transform-origin: 0 0; 37 | } 38 | .leaflet-marker-icon, 39 | .leaflet-marker-shadow { 40 | display: block; 41 | } 42 | /* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */ 43 | /* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */ 44 | .leaflet-container .leaflet-overlay-pane svg, 45 | .leaflet-container .leaflet-marker-pane img, 46 | .leaflet-container .leaflet-shadow-pane img, 47 | .leaflet-container .leaflet-tile-pane img, 48 | .leaflet-container img.leaflet-image-layer { 49 | max-width: none !important; 50 | max-height: none !important; 51 | } 52 | 53 | .leaflet-container.leaflet-touch-zoom { 54 | -ms-touch-action: pan-x pan-y; 55 | touch-action: pan-x pan-y; 56 | } 57 | .leaflet-container.leaflet-touch-drag { 58 | -ms-touch-action: pinch-zoom; 59 | /* Fallback for FF which doesn't support pinch-zoom */ 60 | touch-action: none; 61 | touch-action: pinch-zoom; 62 | } 63 | .leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { 64 | -ms-touch-action: none; 65 | touch-action: none; 66 | } 67 | .leaflet-container { 68 | -webkit-tap-highlight-color: transparent; 69 | } 70 | .leaflet-container a { 71 | -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4); 72 | } 73 | .leaflet-tile { 74 | filter: inherit; 75 | visibility: hidden; 76 | } 77 | .leaflet-tile-loaded { 78 | visibility: inherit; 79 | } 80 | .leaflet-zoom-box { 81 | width: 0; 82 | height: 0; 83 | -moz-box-sizing: border-box; 84 | box-sizing: border-box; 85 | z-index: 800; 86 | } 87 | /* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ 88 | .leaflet-overlay-pane svg { 89 | -moz-user-select: none; 90 | } 91 | 92 | .leaflet-pane { z-index: 400; } 93 | 94 | .leaflet-tile-pane { z-index: 200; } 95 | .leaflet-overlay-pane { z-index: 400; } 96 | .leaflet-shadow-pane { z-index: 500; } 97 | .leaflet-marker-pane { z-index: 600; } 98 | .leaflet-tooltip-pane { z-index: 650; } 99 | .leaflet-popup-pane { z-index: 700; } 100 | 101 | .leaflet-map-pane canvas { z-index: 100; } 102 | .leaflet-map-pane svg { z-index: 200; } 103 | 104 | .leaflet-vml-shape { 105 | width: 1px; 106 | height: 1px; 107 | } 108 | .lvml { 109 | behavior: url(#default#VML); 110 | display: inline-block; 111 | position: absolute; 112 | } 113 | 114 | 115 | /* control positioning */ 116 | 117 | .leaflet-control { 118 | position: relative; 119 | z-index: 800; 120 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ 121 | pointer-events: auto; 122 | } 123 | .leaflet-top, 124 | .leaflet-bottom { 125 | position: absolute; 126 | z-index: 1000; 127 | pointer-events: none; 128 | } 129 | .leaflet-top { 130 | top: 0; 131 | } 132 | .leaflet-right { 133 | right: 0; 134 | } 135 | .leaflet-bottom { 136 | bottom: 0; 137 | } 138 | .leaflet-left { 139 | left: 0; 140 | } 141 | .leaflet-control { 142 | float: left; 143 | clear: both; 144 | } 145 | .leaflet-right .leaflet-control { 146 | float: right; 147 | } 148 | .leaflet-top .leaflet-control { 149 | margin-top: 10px; 150 | } 151 | .leaflet-bottom .leaflet-control { 152 | margin-bottom: 10px; 153 | } 154 | .leaflet-left .leaflet-control { 155 | margin-left: 10px; 156 | } 157 | .leaflet-right .leaflet-control { 158 | margin-right: 10px; 159 | } 160 | 161 | 162 | /* zoom and fade animations */ 163 | 164 | .leaflet-fade-anim .leaflet-tile { 165 | will-change: opacity; 166 | } 167 | .leaflet-fade-anim .leaflet-popup { 168 | opacity: 0; 169 | -webkit-transition: opacity 0.2s linear; 170 | -moz-transition: opacity 0.2s linear; 171 | -o-transition: opacity 0.2s linear; 172 | transition: opacity 0.2s linear; 173 | } 174 | .leaflet-fade-anim .leaflet-map-pane .leaflet-popup { 175 | opacity: 1; 176 | } 177 | .leaflet-zoom-animated { 178 | -webkit-transform-origin: 0 0; 179 | -ms-transform-origin: 0 0; 180 | transform-origin: 0 0; 181 | } 182 | .leaflet-zoom-anim .leaflet-zoom-animated { 183 | will-change: transform; 184 | } 185 | .leaflet-zoom-anim .leaflet-zoom-animated { 186 | -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); 187 | -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); 188 | -o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1); 189 | transition: transform 0.25s cubic-bezier(0,0,0.25,1); 190 | } 191 | .leaflet-zoom-anim .leaflet-tile, 192 | .leaflet-pan-anim .leaflet-tile { 193 | -webkit-transition: none; 194 | -moz-transition: none; 195 | -o-transition: none; 196 | transition: none; 197 | } 198 | 199 | .leaflet-zoom-anim .leaflet-zoom-hide { 200 | visibility: hidden; 201 | } 202 | 203 | 204 | /* cursors */ 205 | 206 | .leaflet-interactive { 207 | cursor: pointer; 208 | } 209 | .leaflet-grab { 210 | cursor: -webkit-grab; 211 | cursor: -moz-grab; 212 | } 213 | .leaflet-crosshair, 214 | .leaflet-crosshair .leaflet-interactive { 215 | cursor: crosshair; 216 | } 217 | .leaflet-popup-pane, 218 | .leaflet-control { 219 | cursor: auto; 220 | } 221 | .leaflet-dragging .leaflet-grab, 222 | .leaflet-dragging .leaflet-grab .leaflet-interactive, 223 | .leaflet-dragging .leaflet-marker-draggable { 224 | cursor: move; 225 | cursor: -webkit-grabbing; 226 | cursor: -moz-grabbing; 227 | } 228 | 229 | /* marker & overlays interactivity */ 230 | .leaflet-marker-icon, 231 | .leaflet-marker-shadow, 232 | .leaflet-image-layer, 233 | .leaflet-pane > svg path, 234 | .leaflet-tile-container { 235 | pointer-events: none; 236 | } 237 | 238 | .leaflet-marker-icon.leaflet-interactive, 239 | .leaflet-image-layer.leaflet-interactive, 240 | .leaflet-pane > svg path.leaflet-interactive { 241 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ 242 | pointer-events: auto; 243 | } 244 | 245 | /* visual tweaks */ 246 | 247 | .leaflet-container { 248 | background: #ddd; 249 | outline: 0; 250 | } 251 | .leaflet-container a { 252 | color: #0078A8; 253 | } 254 | .leaflet-container a.leaflet-active { 255 | outline: 2px solid orange; 256 | } 257 | .leaflet-zoom-box { 258 | border: 2px dotted #38f; 259 | background: rgba(255,255,255,0.5); 260 | } 261 | 262 | 263 | /* general typography */ 264 | .leaflet-container { 265 | font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; 266 | } 267 | 268 | 269 | /* general toolbar styles */ 270 | 271 | .leaflet-bar { 272 | box-shadow: 0 1px 5px rgba(0,0,0,0.65); 273 | border-radius: 4px; 274 | } 275 | .leaflet-bar a, 276 | .leaflet-bar a:hover { 277 | background-color: #fff; 278 | border-bottom: 1px solid #ccc; 279 | width: 26px; 280 | height: 26px; 281 | line-height: 26px; 282 | display: block; 283 | text-align: center; 284 | text-decoration: none; 285 | color: black; 286 | } 287 | .leaflet-bar a, 288 | .leaflet-control-layers-toggle { 289 | background-position: 50% 50%; 290 | background-repeat: no-repeat; 291 | display: block; 292 | } 293 | .leaflet-bar a:hover { 294 | background-color: #f4f4f4; 295 | } 296 | .leaflet-bar a:first-child { 297 | border-top-left-radius: 4px; 298 | border-top-right-radius: 4px; 299 | } 300 | .leaflet-bar a:last-child { 301 | border-bottom-left-radius: 4px; 302 | border-bottom-right-radius: 4px; 303 | border-bottom: none; 304 | } 305 | .leaflet-bar a.leaflet-disabled { 306 | cursor: default; 307 | background-color: #f4f4f4; 308 | color: #bbb; 309 | } 310 | 311 | .leaflet-touch .leaflet-bar a { 312 | width: 30px; 313 | height: 30px; 314 | line-height: 30px; 315 | } 316 | .leaflet-touch .leaflet-bar a:first-child { 317 | border-top-left-radius: 2px; 318 | border-top-right-radius: 2px; 319 | } 320 | .leaflet-touch .leaflet-bar a:last-child { 321 | border-bottom-left-radius: 2px; 322 | border-bottom-right-radius: 2px; 323 | } 324 | 325 | /* zoom control */ 326 | 327 | .leaflet-control-zoom-in, 328 | .leaflet-control-zoom-out { 329 | font: bold 18px 'Lucida Console', Monaco, monospace; 330 | text-indent: 1px; 331 | } 332 | 333 | .leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { 334 | font-size: 22px; 335 | } 336 | 337 | 338 | /* layers control */ 339 | 340 | .leaflet-control-layers { 341 | box-shadow: 0 1px 5px rgba(0,0,0,0.4); 342 | background: #fff; 343 | border-radius: 5px; 344 | } 345 | .leaflet-control-layers-toggle { 346 | background-image: url(images/layers.png); 347 | width: 36px; 348 | height: 36px; 349 | } 350 | .leaflet-retina .leaflet-control-layers-toggle { 351 | background-image: url(images/layers-2x.png); 352 | background-size: 26px 26px; 353 | } 354 | .leaflet-touch .leaflet-control-layers-toggle { 355 | width: 44px; 356 | height: 44px; 357 | } 358 | .leaflet-control-layers .leaflet-control-layers-list, 359 | .leaflet-control-layers-expanded .leaflet-control-layers-toggle { 360 | display: none; 361 | } 362 | .leaflet-control-layers-expanded .leaflet-control-layers-list { 363 | display: block; 364 | position: relative; 365 | } 366 | .leaflet-control-layers-expanded { 367 | padding: 6px 10px 6px 6px; 368 | color: #333; 369 | background: #fff; 370 | } 371 | .leaflet-control-layers-scrollbar { 372 | overflow-y: scroll; 373 | overflow-x: hidden; 374 | padding-right: 5px; 375 | } 376 | .leaflet-control-layers-selector { 377 | margin-top: 2px; 378 | position: relative; 379 | top: 1px; 380 | } 381 | .leaflet-control-layers label { 382 | display: block; 383 | } 384 | .leaflet-control-layers-separator { 385 | height: 0; 386 | border-top: 1px solid #ddd; 387 | margin: 5px -10px 5px -6px; 388 | } 389 | 390 | /* Default icon URLs */ 391 | .leaflet-default-icon-path { 392 | background-image: url(images/marker-icon.png); 393 | } 394 | 395 | 396 | /* attribution and scale controls */ 397 | 398 | .leaflet-container .leaflet-control-attribution { 399 | background: #fff; 400 | background: rgba(255, 255, 255, 0.7); 401 | margin: 0; 402 | } 403 | .leaflet-control-attribution, 404 | .leaflet-control-scale-line { 405 | padding: 0 5px; 406 | color: #333; 407 | } 408 | .leaflet-control-attribution a { 409 | text-decoration: none; 410 | } 411 | .leaflet-control-attribution a:hover { 412 | text-decoration: underline; 413 | } 414 | .leaflet-container .leaflet-control-attribution, 415 | .leaflet-container .leaflet-control-scale { 416 | font-size: 11px; 417 | } 418 | .leaflet-left .leaflet-control-scale { 419 | margin-left: 5px; 420 | } 421 | .leaflet-bottom .leaflet-control-scale { 422 | margin-bottom: 5px; 423 | } 424 | .leaflet-control-scale-line { 425 | border: 2px solid #777; 426 | border-top: none; 427 | line-height: 1.1; 428 | padding: 2px 5px 1px; 429 | font-size: 11px; 430 | white-space: nowrap; 431 | overflow: hidden; 432 | -moz-box-sizing: border-box; 433 | box-sizing: border-box; 434 | 435 | background: #fff; 436 | background: rgba(255, 255, 255, 0.5); 437 | } 438 | .leaflet-control-scale-line:not(:first-child) { 439 | border-top: 2px solid #777; 440 | border-bottom: none; 441 | margin-top: -2px; 442 | } 443 | .leaflet-control-scale-line:not(:first-child):not(:last-child) { 444 | border-bottom: 2px solid #777; 445 | } 446 | 447 | .leaflet-touch .leaflet-control-attribution, 448 | .leaflet-touch .leaflet-control-layers, 449 | .leaflet-touch .leaflet-bar { 450 | box-shadow: none; 451 | } 452 | .leaflet-touch .leaflet-control-layers, 453 | .leaflet-touch .leaflet-bar { 454 | border: 2px solid rgba(0,0,0,0.2); 455 | background-clip: padding-box; 456 | } 457 | 458 | 459 | /* popup */ 460 | 461 | .leaflet-popup { 462 | position: absolute; 463 | text-align: center; 464 | margin-bottom: 20px; 465 | } 466 | .leaflet-popup-content-wrapper { 467 | padding: 1px; 468 | text-align: left; 469 | border-radius: 12px; 470 | } 471 | .leaflet-popup-content { 472 | margin: 13px 19px; 473 | line-height: 1.4; 474 | } 475 | .leaflet-popup-content p { 476 | margin: 18px 0; 477 | } 478 | .leaflet-popup-tip-container { 479 | width: 40px; 480 | height: 20px; 481 | position: absolute; 482 | left: 50%; 483 | margin-left: -20px; 484 | overflow: hidden; 485 | pointer-events: none; 486 | } 487 | .leaflet-popup-tip { 488 | width: 17px; 489 | height: 17px; 490 | padding: 1px; 491 | 492 | margin: -10px auto 0; 493 | 494 | -webkit-transform: rotate(45deg); 495 | -moz-transform: rotate(45deg); 496 | -ms-transform: rotate(45deg); 497 | -o-transform: rotate(45deg); 498 | transform: rotate(45deg); 499 | } 500 | .leaflet-popup-content-wrapper, 501 | .leaflet-popup-tip { 502 | background: white; 503 | color: #333; 504 | box-shadow: 0 3px 14px rgba(0,0,0,0.4); 505 | } 506 | .leaflet-container a.leaflet-popup-close-button { 507 | position: absolute; 508 | top: 0; 509 | right: 0; 510 | padding: 4px 4px 0 0; 511 | border: none; 512 | text-align: center; 513 | width: 18px; 514 | height: 14px; 515 | font: 16px/14px Tahoma, Verdana, sans-serif; 516 | color: #c3c3c3; 517 | text-decoration: none; 518 | font-weight: bold; 519 | background: transparent; 520 | } 521 | .leaflet-container a.leaflet-popup-close-button:hover { 522 | color: #999; 523 | } 524 | .leaflet-popup-scrolled { 525 | overflow: auto; 526 | border-bottom: 1px solid #ddd; 527 | border-top: 1px solid #ddd; 528 | } 529 | 530 | .leaflet-oldie .leaflet-popup-content-wrapper { 531 | zoom: 1; 532 | } 533 | .leaflet-oldie .leaflet-popup-tip { 534 | width: 24px; 535 | margin: 0 auto; 536 | 537 | -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; 538 | filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); 539 | } 540 | .leaflet-oldie .leaflet-popup-tip-container { 541 | margin-top: -1px; 542 | } 543 | 544 | .leaflet-oldie .leaflet-control-zoom, 545 | .leaflet-oldie .leaflet-control-layers, 546 | .leaflet-oldie .leaflet-popup-content-wrapper, 547 | .leaflet-oldie .leaflet-popup-tip { 548 | border: 1px solid #999; 549 | } 550 | 551 | 552 | /* div icon */ 553 | 554 | .leaflet-div-icon { 555 | background: #fff; 556 | border: 1px solid #666; 557 | } 558 | 559 | 560 | /* Tooltip */ 561 | /* Base styles for the element that has a tooltip */ 562 | .leaflet-tooltip { 563 | position: absolute; 564 | padding: 6px; 565 | background-color: #fff; 566 | border: 1px solid #fff; 567 | border-radius: 3px; 568 | color: #222; 569 | white-space: nowrap; 570 | -webkit-user-select: none; 571 | -moz-user-select: none; 572 | -ms-user-select: none; 573 | user-select: none; 574 | pointer-events: none; 575 | box-shadow: 0 1px 3px rgba(0,0,0,0.4); 576 | } 577 | .leaflet-tooltip.leaflet-clickable { 578 | cursor: pointer; 579 | pointer-events: auto; 580 | } 581 | .leaflet-tooltip-top:before, 582 | .leaflet-tooltip-bottom:before, 583 | .leaflet-tooltip-left:before, 584 | .leaflet-tooltip-right:before { 585 | position: absolute; 586 | pointer-events: none; 587 | border: 6px solid transparent; 588 | background: transparent; 589 | content: ""; 590 | } 591 | 592 | /* Directions */ 593 | 594 | .leaflet-tooltip-bottom { 595 | margin-top: 6px; 596 | } 597 | .leaflet-tooltip-top { 598 | margin-top: -6px; 599 | } 600 | .leaflet-tooltip-bottom:before, 601 | .leaflet-tooltip-top:before { 602 | left: 50%; 603 | margin-left: -6px; 604 | } 605 | .leaflet-tooltip-top:before { 606 | bottom: 0; 607 | margin-bottom: -12px; 608 | border-top-color: #fff; 609 | } 610 | .leaflet-tooltip-bottom:before { 611 | top: 0; 612 | margin-top: -12px; 613 | margin-left: -6px; 614 | border-bottom-color: #fff; 615 | } 616 | .leaflet-tooltip-left { 617 | margin-left: -6px; 618 | } 619 | .leaflet-tooltip-right { 620 | margin-left: 6px; 621 | } 622 | .leaflet-tooltip-left:before, 623 | .leaflet-tooltip-right:before { 624 | top: 50%; 625 | margin-top: -6px; 626 | } 627 | .leaflet-tooltip-left:before { 628 | right: 0; 629 | margin-right: -12px; 630 | border-left-color: #fff; 631 | } 632 | .leaflet-tooltip-right:before { 633 | left: 0; 634 | margin-left: -12px; 635 | border-right-color: #fff; 636 | } 637 | -------------------------------------------------------------------------------- /libs/leafletfix/leafletfix.css: -------------------------------------------------------------------------------- 1 | /* Work around CSS properties introduced on img by bootstrap */ 2 | img.leaflet-tile { 3 | padding: 0; 4 | margin: 0; 5 | border-radius: 0; 6 | border: none; 7 | } 8 | .info { 9 | padding: 6px 8px; 10 | font: 14px/16px Arial, Helvetica, sans-serif; 11 | background: white; 12 | background: rgba(255,255,255,0.8); 13 | box-shadow: 0 0 15px rgba(0,0,0,0.2); 14 | border-radius: 5px; 15 | } 16 | .legend { 17 | line-height: 18px; 18 | color: #555; 19 | } 20 | .legend svg text { 21 | fill: #555; 22 | } 23 | .legend svg line { 24 | stroke: #555; 25 | } 26 | .legend i { 27 | width: 18px; 28 | height: 18px; 29 | margin-right: 4px; 30 | opacity: 0.7; 31 | display: inline-block; 32 | vertical-align: top; 33 | /*For IE 7*/ 34 | zoom: 1; 35 | *display: inline; 36 | } 37 | -------------------------------------------------------------------------------- /libs/remark-css/default-fonts.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Yanone+Kaffeesatz); 2 | @import url(https://fonts.googleapis.com/css?family=Droid+Serif:400,700,400italic); 3 | @import url(https://fonts.googleapis.com/css?family=Source+Code+Pro:400,700); 4 | 5 | body { font-family: 'Droid Serif', 'Palatino Linotype', 'Book Antiqua', Palatino, 'Microsoft YaHei', 'Songti SC', serif; } 6 | h1, h2, h3 { 7 | font-family: 'Yanone Kaffeesatz'; 8 | font-weight: normal; 9 | } 10 | .remark-code, .remark-inline-code { font-family: 'Source Code Pro', 'Lucida Console', Monaco, monospace; } 11 | -------------------------------------------------------------------------------- /libs/remark-css/default.css: -------------------------------------------------------------------------------- 1 | a, a > code { 2 | color: rgb(249, 38, 114); 3 | text-decoration: none; 4 | } 5 | .footnote { 6 | position: absolute; 7 | bottom: 3em; 8 | padding-right: 4em; 9 | font-size: 90%; 10 | } 11 | .remark-code-line-highlighted { background-color: #ffff88; } 12 | 13 | .inverse { 14 | background-color: #272822; 15 | color: #d6d6d6; 16 | text-shadow: 0 0 20px #333; 17 | } 18 | .inverse h1, .inverse h2, .inverse h3 { 19 | color: #f3f3f3; 20 | } 21 | /* Two-column layout */ 22 | .left-column { 23 | color: #777; 24 | width: 20%; 25 | height: 92%; 26 | float: left; 27 | } 28 | .left-column h2:last-of-type, .left-column h3:last-child { 29 | color: #000; 30 | } 31 | .right-column { 32 | width: 75%; 33 | float: right; 34 | padding-top: 1em; 35 | } 36 | .pull-left { 37 | float: left; 38 | width: 47%; 39 | } 40 | .pull-right { 41 | float: right; 42 | width: 47%; 43 | } 44 | .pull-right ~ * { 45 | clear: both; 46 | } 47 | img, video, iframe { 48 | max-width: 100%; 49 | } 50 | blockquote { 51 | border-left: solid 5px lightgray; 52 | padding-left: 1em; 53 | } 54 | .remark-slide table { 55 | margin: auto; 56 | border-top: 1px solid #666; 57 | border-bottom: 1px solid #666; 58 | } 59 | .remark-slide table thead th { border-bottom: 1px solid #ddd; } 60 | th, td { padding: 5px; } 61 | .remark-slide thead, .remark-slide tfoot, .remark-slide tr:nth-child(even) { background: #eee } 62 | 63 | @page { margin: 0; } 64 | @media print { 65 | .remark-slide-scaler { 66 | width: 100% !important; 67 | height: 100% !important; 68 | transform: scale(1) !important; 69 | top: 0 !important; 70 | left: 0 !important; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /libs/rstudio_leaflet/images/1px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrrs/r_beginners_workshop/1971ded361f43811a542583a43a0e82fd256642e/libs/rstudio_leaflet/images/1px.png -------------------------------------------------------------------------------- /libs/rstudio_leaflet/rstudio_leaflet.css: -------------------------------------------------------------------------------- 1 | .leaflet-tooltip.leaflet-tooltip-text-only, 2 | .leaflet-tooltip.leaflet-tooltip-text-only:before, 3 | .leaflet-tooltip.leaflet-tooltip-text-only:after { 4 | background: none; 5 | border: none; 6 | box-shadow: none; 7 | } 8 | 9 | .leaflet-tooltip.leaflet-tooltip-text-only.leaflet-tooltip-left { 10 | margin-left: 5px; 11 | } 12 | 13 | .leaflet-tooltip.leaflet-tooltip-text-only.leaflet-tooltip-right { 14 | margin-left: -5px; 15 | } 16 | 17 | .leaflet-tooltip:after { 18 | border-right: 6px solid transparent; 19 | /* right: -16px; */ 20 | } 21 | 22 | .leaflet-popup-pane .leaflet-popup-tip-container { 23 | /* when the tooltip container is clicked, it is closed */ 24 | pointer-events: all; 25 | /* tooltips should display the "hand" icon, just like .leaflet-interactive*/ 26 | cursor: pointer; 27 | } 28 | 29 | /* have the widget be displayed in the right 'layer' */ 30 | .leaflet-map-pane { 31 | z-index: auto; 32 | } 33 | -------------------------------------------------------------------------------- /presentation.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Intro to R for Data Science" 3 | subtitle: "Beginner's workshop" 4 | author: "AbdulMajedRaja RS" 5 | output: 6 | xaringan::moon_reader: 7 | chakra: libs/remark-latest.min.js 8 | lib_dir: libs 9 | nature: 10 | highlightStyle: github 11 | highlightLines: true 12 | countIncrementalSlides: false 13 | --- 14 | 15 | ```{r setup, include=FALSE} 16 | options(htmltools.dir.version = FALSE) 17 | ``` 18 | 19 | 20 | # About Me 21 | 22 | - Studied at **Government College of Technology, Coimbatore** 23 | 24 | - Bengaluru R user group **Organizer** 25 | 26 | - R Packages Developer ( `coinmarketcapr`, `itunesr`) 27 | 28 | 29 | --- 30 | 31 | class: inverse 32 | 33 | # Disclaimer: 34 | 35 | - This workshop is **NOT** going to make you a Data Scientist **in a day**. 36 | 37 | - The objective is to help you get a flavor of R and how it is used in Data Science 38 | 39 | - Thus, get you ready to embark on your own journey to become a Data Scientist who uses R 40 | 41 | 42 | --- 43 | 44 | # Content: 45 | 46 | This presentation's content is heavily borrowed from the book [**R for Data Science**](https://r4ds.had.co.nz) by **Garrett Grolemund** and **Hadley Wickham** 47 | 48 | 49 | .center[] 50 | 51 | --- 52 | 53 | 54 | # About R 55 | 56 | - R is a language and environment for statistical computing and graphics. (Ref: [`r-project.org`](https://www.r-project.org/about.html)) 57 | 58 | - R was created by Ross Ihaka and Robert Gentleman at the University of Auckland, New Zealand 59 | 60 | - R is Free 61 | 62 | - R can be extended (easily) via *packages*. 63 | 64 | - R is an interpreted language 65 | 66 | 67 | .right[![image](images/Rlogo.png)] 68 | 69 | 70 | --- 71 | class: inverse, center, middle 72 | 73 | # R Interpreter / Console / GUI 74 | ## Demo 75 | --- 76 | 77 | # About RStudio 78 | 79 | 80 | - RStudio is a **free and open-source IDE** for R, released by the company **RStudio, Inc.** 81 | 82 | - RStudio and its team regularly contribute to R community by releasing new packages, such as: 83 | - `tidyverse` 84 | - `shiny` 85 | - `knitr` 86 | 87 | 88 | .right[] 89 | 90 | 91 | --- 92 | class: inverse, center, middle 93 | 94 | # RStudio 95 | ## Demo 96 | 97 | 98 | --- 99 | 100 | # R Ecosystem 101 | 102 | Like `Python`, `R`'s strength lies in its Ecosystem. **Why R?** - R Packages 103 | 104 | 105 | ### Growth 106 | 107 | 108 |
109 | 111 |
Source: @daroczig 112 |
113 | 114 |
115 | 116 | --- 117 | class: inverse, center, middle 118 | 119 | # Basics of R Programming 120 | 121 | --- 122 | 123 | # Hello, World! 124 | 125 | 126 | The traditional first step - **Hello, World!**: 127 | 128 | -- 129 | 130 | ```{r eval=TRUE, tidy=FALSE} 131 | print("Hello, World!") #<< 132 | 133 | ``` 134 | 135 |
136 | 137 | ###.center[That's one small step for a man, one giant leap for mankind] 138 | ###.center[Neil Armstrong] 139 | 140 | --- 141 | 142 | # Arithmetic Operations 143 | 144 | ```{r} 145 | 2 + 3 146 | ``` 147 | 148 | ```{r} 149 | 50000 * 42222 150 | ``` 151 | 152 | ```{r} 153 | 2304 / 233 154 | ``` 155 | 156 | ```{r} 157 | (33 + 44 ) * 232 / 12 158 | ``` 159 | 160 | --- 161 | 162 | # Assignment Operators 163 | 164 | ### .center[`<-` **Arrow (Less-than < and Minus - )**] 165 | 166 | ### .center[`=` **(Equal Sign)**] 167 | 168 | ```{r} 169 | (x <- 2 + 3) 170 | ``` 171 | 172 | 173 | ```{r} 174 | (y = x ** 4) #<< 175 | ``` 176 | 177 | ```{r} 178 | 5 * 9 -> a 179 | a + 3 180 | ``` 181 | 182 | --- 183 | 184 | 185 | 186 | # Objects 187 | 188 | * The entities R operates on are technically known as `objects`. 189 | 190 | Example: Vector of numeric 191 | 192 | 193 | ```{r} 194 | vector_of_numeric <- c(2,4,5) 195 | 196 | typeof(vector_of_numeric) 197 | ``` 198 | 199 | --- 200 | 201 | # Vectors 202 | 203 | - Atomic Vectors - Homogeneous Data Type 204 | 205 | - logical 206 | - integer 207 | - double 208 | - character 209 | - *complex* 210 | - *raw* 211 | 212 | - Lists - (Recursive Vectors) Heterogeneous Data Type 213 | 214 | - `NULL` is used to represent absence of a vector 215 | 216 | **Vectors + Attributes (Additional Meta Data) = Augmented vectors** 217 | 218 | * Factors are built on top of integer vectors. 219 | * Dates and date-times are built on top of numeric vectors. 220 | * Data frames and tibbles are built on top of lists. 221 | 222 | 223 | --- 224 | 225 | ### Numeric Vector 226 | Each element of the numeric vector should be a number. 227 | ```{r} 228 | nummy <- c(2,3,4) 229 | 230 | nummy_int <- c(1L,2L,3L) 231 | ``` 232 | 233 | 234 | ```{r} 235 | typeof(nummy) 236 | 237 | typeof(nummy_int) 238 | ``` 239 | 240 | ```{r} 241 | is.numeric(nummy) 242 | is.numeric(nummy_int) 243 | 244 | is.double(nummy) 245 | is.double(nummy_int) 246 | ``` 247 | 248 | 249 | --- 250 | 251 | ### Character Vector 252 | 253 | ```{r} 254 | 255 | types <- c("int","double","character") 256 | 257 | types 258 | ``` 259 | 260 | 261 | ```{r} 262 | typeof(types) 263 | 264 | length(types) 265 | 266 | ``` 267 | 268 | ```{r} 269 | is.numeric(types) 270 | is.character(types) 271 | ``` 272 | --- 273 | 274 | ### Logical Vector 275 | 276 | ```{r} 277 | 278 | logicals <- c(TRUE,F,TRUE,T, FALSE) 279 | 280 | logicals 281 | ``` 282 | --- 283 | 284 | # Coersion 285 | 286 | ## Typecasting - Explicit 287 | 288 | 289 | ```{r} 290 | 291 | money_in_chars <- c("20","35","33") 292 | 293 | typeof(money_in_chars) 294 | 295 | ``` 296 | 297 | ```{r} 298 | money_money <- as.numeric(money_in_chars) 299 | 300 | money_money 301 | 302 | typeof(money_money) 303 | ``` 304 | 305 | --- 306 | 307 | ## Typecasting - Implicit 308 | 309 | ```{r} 310 | money_money <- as.numeric(money_in_chars) 311 | 312 | money_money 313 | 314 | typeof(money_money) 315 | 316 | ``` 317 | 318 | ```{r} 319 | 320 | new_money <- c(money_money,"33") 321 | 322 | new_money 323 | 324 | typeof(new_money) 325 | 326 | ``` 327 | 328 | 329 | --- 330 | 331 | #Vector - Accessing 332 | 333 | ```{r} 334 | 335 | month.abb #in-built character vector with Month Abbreviations 336 | 337 | month.abb[2] 338 | 339 | ``` 340 | 341 | ```{r} 342 | month.abb[4:7] 343 | ``` 344 | 345 | ```{r} 346 | month.abb[c(2,5,7,10)] 347 | ``` 348 | 349 | --- 350 | # Vector Manipulation 351 | 352 | ## Appending 353 | 354 | ```{r} 355 | days <- c("Mon","Tue","Wed") 356 | 357 | days 358 | ``` 359 | 360 | ```{r} 361 | week_end <- c("Sat","Sun") 362 | 363 | more_days <- c(days,"Thu","Fri",week_end) 364 | 365 | more_days 366 | ``` 367 | 368 | --- 369 | 370 | # Vector - Arithmetic 371 | 372 | ```{r} 373 | 374 | set.seed(122) 375 | 376 | so_many_numbers <- runif(10, min = 10, max = 100) 377 | 378 | so_many_numbers 379 | ``` 380 | 381 | ```{r} 382 | so_many_numbers * 200 383 | ``` 384 | 385 | --- 386 | # Factors 387 | 388 | * In R, factors are used to work with categorical variables, variables that have a fixed and known set of possible values. 389 | 390 | * Useful with Characters where non-Alphabetical Ordering is required 391 | 392 | ```{r} 393 | days <- c("Thu","Wed","Sun") 394 | 395 | sort(days) 396 | ``` 397 | 398 | 399 | ```{r} 400 | 401 | week_levels <- c("Mon","Tue","Wed","Thu","Fri","Sat","Sun") 402 | 403 | 404 | (days_f <- factor(days, levels = week_levels)) 405 | 406 | ``` 407 | 408 | ```{r} 409 | sort(days_f) 410 | ``` 411 | 412 | --- 413 | 414 | # List 415 | 416 | Lists are a step up in complexity from atomic vectors: each element can be any type, not just vectors. 417 | 418 | ```{r} 419 | 420 | (a_list <- list("abcd",123,1:12,month.abb)) 421 | 422 | ``` 423 | --- 424 | 425 | # List Accessing 426 | 427 | ```{r} 428 | a_list[[1]] 429 | 430 | a_list[[4]][4] 431 | ``` 432 | 433 | 434 | --- 435 | 436 | # Matrix 437 | 438 | ```{r} 439 | new_m <- matrix(data = 1:12, nrow = 3) 440 | 441 | new_m 442 | ``` 443 | 444 | ```{r} 445 | new_m * 20 446 | ``` 447 | 448 | 449 | ```{r} 450 | dim(new_m) 451 | new_m[2,3] 452 | ``` 453 | --- 454 | # Dataframe 455 | 456 | ## Tabular Structure 457 | 458 | * dimension 459 | * row.names 460 | * col.names 461 | 462 | 463 | ```{r} 464 | colleges <- c("CIT","GCT","PSG") 465 | 466 | year <- c(2019,2018,2017) 467 | 468 | db <- data.frame(college_names = colleges, year_since = year) 469 | 470 | db 471 | 472 | 473 | ``` 474 | 475 | --- 476 | # Dataframe Manipulation 477 | 478 | ```{r} 479 | db$college_names 480 | ``` 481 | 482 | ```{r} 483 | db[2,2] <- 1990 484 | 485 | db 486 | 487 | ``` 488 | 489 | ```{r} 490 | db[,"year_since"] 491 | ``` 492 | 493 | --- 494 | 495 | # Loops & Iterators 496 | 497 | ## For Loop 498 | 499 | ```{r} 500 | for (month_name in month.abb[1:4]) { 501 | print(paste("This month", month_name, "beautiful!!!")) 502 | } 503 | 504 | ``` 505 | 506 | As you move forward, Check the family of `apply` functions - `sapply()`, `tapply()`, `lapply()`, `apply()`. 507 | 508 | For advanced functional programming, refer `purrr` package 509 | 510 | --- 511 | 512 | # Logical Operations 513 | 514 | ## %in% operator 515 | 516 | ```{r} 517 | 518 | iris$Species %in% "virginica" 519 | 520 | ``` 521 | 522 | --- 523 | 524 | ## Logical Operators 525 | 526 | ```{r} 527 | 1:10 > 5 528 | ``` 529 | 530 | ```{r} 531 | 1:10 == 4 532 | ``` 533 | 534 | ```{r} 535 | !1:10 == 4 536 | ``` 537 | 538 | --- 539 | # Conditions 540 | 541 | ```{r} 542 | if (iris$Sepal.Length[2]>5) { 543 | print("it is gt 5") 544 | } else print("it is not") 545 | ``` 546 | 547 | 548 | ```{r} 549 | if (iris$Sepal.Length>10) {print("hello")} 550 | ``` 551 | 552 | ```{r} 553 | ifelse(iris$Sepal.Length>6, "more_than_10","les_than_10") 554 | ``` 555 | 556 | --- 557 | 558 | # Functions 559 | 560 | ## Types 561 | 562 | - Base-R functions (`mean()`, `plot()`, `lm()`) 563 | - Package functions (`dplyr::mutate()`, `stringr::str_detect()`) 564 | - User-defined functions 565 | 566 | ```{r} 567 | workshop_hate_message <- function(name = "No one", n = 3) { 568 | text_to_print <- paste(name, "hate(s)", "this workshop") 569 | for(i in 1:n) { 570 | print(text_to_print) 571 | } 572 | 573 | } 574 | 575 | workshop_hate_message("All of us",4) 576 | ``` 577 | 578 | 579 | --- 580 | 581 | # Packages 582 | 583 | ## Package Installation & Loading 584 | 585 | ### From CRAN (usually Stable Version) 586 | 587 | ```{r eval=FALSE} 588 | install.packages("itunesr") 589 | ``` 590 | 591 | **From Github (usually Development Version)** 592 | 593 | ```{r eval=FALSE} 594 | #install.packages("devtools") 595 | devtools::install_github("amrrs/itunesr") 596 | ``` 597 | 598 | ### Loading 599 | 600 | ```{r eval=FALSE} 601 | library(itunesr) 602 | ``` 603 | 604 | --- 605 | 606 | # Help 607 | 608 | ## using `help()` 609 | 610 | ```{r eval=FALSE} 611 | help("runif") 612 | ``` 613 | 614 | ## using ? 615 | 616 | ```{r eval=FALSE} 617 | ?sample 618 | ``` 619 | 620 | --- 621 | 622 | # Help - Example 623 | 624 | ```{r} 625 | example("for") 626 | ``` 627 | 628 | --- 629 | 630 | # Packages Vignette 631 | 632 | ```{r eval=FALSE} 633 | vignette("dplyr") 634 | 635 | browseVignettes("dplyr") 636 | ``` 637 | 638 | 639 | 640 | --- 641 | class: inverse, center, middle 642 | 643 | # Data wrangling and Visualization using Tidyverse 644 | 645 | --- 646 | class: center, middle 647 | 648 | # Data Science Framework 649 | 650 | 651 | There are now like, you know, a billion venn diagrams showing you what data science is. But to me I think the definition is pretty simple. Whenever you're struggling with data, trying to understand what's going on with data, whenever you're trying to turn that **raw data into insight and understanding and discoveries**. I think that's **Data Science.**" - Hadley Wickham 652 | 653 |
654 | 656 |
Source: Hadley Wickham 657 |
658 | 659 |
660 | 661 | --- 662 | 663 | # Tidyverse 664 | 665 | 666 | - An opinionated collection of R packages designed for data science. 667 | - All packages share an underlying design *philosophy, grammar, and data structures*. 668 | 669 | ```{r eval=FALSE} 670 | install.packages("tidyverse") 671 | ``` 672 | 673 | ### tidyverse packages 674 | 675 | ```{r} 676 | tidyverse::tidyverse_packages() 677 | ``` 678 | 679 | --- 680 | 681 | # Loading the Library 682 | 683 | ```{r} 684 | 685 | library(tidyverse) 686 | 687 | ``` 688 | 689 | 690 | --- 691 | 692 | # Input Data 693 | 694 | Reading the dataset 695 | 696 | ```{r warning=FALSE, message=FALSE} 697 | 698 | #kaggle <- read_csv("data/kaggle_survey_2018.csv") 699 | 700 | kaggle <- read_csv("data/kaggle_survey_2018.csv", skip = 1) 701 | ``` 702 | 703 | --- 704 | 705 | # Basic Stats 706 | 707 | ### Dimension (Rows Column) 708 | 709 | ```{r} 710 | 711 | dim(kaggle) 712 | ``` 713 | 714 | ```{r} 715 | glimpse(kaggle) 716 | ``` 717 | 718 | --- 719 | 720 | class: inverse, center 721 | 722 | # Dataset Overview 723 | ## Demo on RStudio 724 | 725 | --- 726 | 727 | # Data Questions (Business Problem) 728 | 729 | - What's the percentage of Male and Female respondents? 730 | 731 | - What are the top 5 countries? 732 | 733 | --- 734 | 735 | # dyplr verbs 736 | 737 | - `mutate()` - adds new variables that are functions of existing variables 738 | - `select()` - picks variables based on their names. 739 | - `filter()` - picks cases based on their values. 740 | - `summarise()` - reduces multiple values down to a single summary. 741 | - `arrange()` - changes the ordering of the rows. 742 | 743 | --- 744 | 745 | # Introducing %>% Pipe Operator 746 | 747 | - The pipe, `%>%`, comes from the magrittr package by Stefan Milton Bache 748 | 749 | - **Output of LHS** is given as the **input (first argument) of RHS** 750 | 751 | ### Example 752 | 753 | ```{r} 754 | 755 | kaggle %>% dim() 756 | 757 | ``` 758 | 759 | Although doesn't make much sense to use `%>%` in this context, Hope it explains the function. 760 | 761 | --- 762 | 763 | # Percentage of Male and Female 764 | 765 | * Column name - `What is your gender? - Selected Choice` 766 | 767 | ### Pseudo-code 768 | 769 | - `group_by` the `kaggle` dataframe on column `What is your gender? - Selected Choice` 770 | - `count` the values 771 | - calculate `percentage` value from the `count`s 772 | 773 | --- 774 | 775 | # % of Male and Female - Group By & Count - Method 1 776 | 777 | 778 | ```{r} 779 | kaggle %>% 780 | group_by(`What is your gender? - Selected Choice`) %>% 781 | summarise(n = n()) 782 | ``` 783 | 784 | --- 785 | 786 | # % of Male and Female - Group By & Count - Method 2 787 | 788 | ```{r} 789 | kaggle %>% 790 | group_by(`What is your gender? - Selected Choice`) %>% 791 | count() 792 | ``` 793 | 794 | --- 795 | 796 | # % of Male and Female - Group By & Count - Sorted 797 | 798 | ```{r} 799 | kaggle %>% 800 | group_by(`What is your gender? - Selected Choice`) %>% 801 | count() %>% 802 | arrange(desc(n)) 803 | ``` 804 | 805 | 806 | --- 807 | 808 | # % of Male and Female - Percentage 809 | 810 | 811 | ```{r} 812 | kaggle %>% 813 | group_by(`What is your gender? - Selected Choice`) %>% 814 | count() %>% 815 | ungroup() %>% 816 | mutate(perc = round(n / sum(n),2)) 817 | ``` 818 | 819 | --- 820 | 821 | # % of Male and Female - Nice_Looking_Table 822 | 823 | 824 | ```{r} 825 | kaggle %>% 826 | group_by(`What is your gender? - Selected Choice`) %>% 827 | count() %>% 828 | ungroup() %>% 829 | mutate(perc = round(n / sum(n),2)) %>% 830 | knitr::kable(format = "html") 831 | ``` 832 | 833 | --- 834 | 835 | class: inverse,center,middle 836 | 837 | # But, Wait!!! 838 | ## Go Back and See 839 | ### If you have only `Male` and `Female`? 840 | 841 | 842 | --- 843 | 844 | class: inverse,center,middle 845 | 846 | # Time for some cleaning 847 | ## In the form of `filter()`ing 848 | 849 | --- 850 | 851 | # % of Male and Female - Filtered_Nice 852 | 853 | 854 | ```{r} 855 | kaggle %>% 856 | filter(`What is your gender? - Selected Choice` %in% c("Male","Female")) %>% 857 | group_by(`What is your gender? - Selected Choice`) %>% 858 | count() %>% 859 | ungroup() %>% 860 | mutate(perc = round(n / sum(n),2)) %>% 861 | knitr::kable(format = "html") 862 | ``` 863 | 864 | 865 | --- 866 | 867 | class: inverse,center,middle 868 | 869 | # An Awkward column name, isn't it??! 870 | 871 | --- 872 | 873 | # % of Male and Female - All_Nice_Table 874 | 875 | ```{r} 876 | library(scales) #for Percentage Formatting 877 | 878 | kaggle %>% 879 | filter(`What is your gender? - Selected Choice` %in% c("Male","Female")) %>% 880 | group_by(`What is your gender? - Selected Choice`) %>% 881 | count() %>% 882 | ungroup() %>% 883 | mutate(perc = round(n / sum(n),2)) %>% 884 | mutate(perc = scales::percent(perc)) %>% 885 | rename(Gender = `What is your gender? - Selected Choice`, 886 | Count = n, 887 | Percentage = perc) %>% 888 | knitr::kable(format = "html") 889 | ``` 890 | 891 | --- 892 | 893 | # Top 5 Countries 894 | 895 | * Column name - `In which country do you currently reside?` 896 | 897 | 898 | ### Pseudo-code 899 | 900 | - `count` number of respondents from each country 901 | - `arrange` countries in descending order based on their count value 902 | - `top 5` in the list is the output 903 | 904 | --- 905 | 906 | # Top 5 Countries - Code 907 | 908 | ```{r} 909 | kaggle %>% 910 | count(`In which country do you currently reside?`) %>% 911 | arrange(desc(n)) %>% 912 | top_n(5) %>% 913 | knitr::kable(format = "html") 914 | ``` 915 | 916 | 917 | --- 918 | class: inverse,center,middle 919 | 920 | # Is `Other` a country name??? 921 | 922 | --- 923 | 924 | # Top 5 Countries 925 | 926 | ```{r} 927 | kaggle %>% 928 | filter(!`In which country do you currently reside?` %in% "Other") %>% 929 | count(`In which country do you currently reside?`) %>% 930 | rename(Country = `In which country do you currently reside?`) %>% 931 | arrange(desc(n)) %>% 932 | top_n(5) %>% 933 | knitr::kable(format = "html") 934 | ``` 935 | 936 | 937 | --- 938 | class: inverse,center,middle 939 | 940 | # Table is nice, but a visually appealing plot is Nicer 941 | ## 😉 942 | 943 | 944 | --- 945 | 946 | # Top 5 Countries - Plot #1 947 | 948 | ```{r countries1, eval = FALSE} 949 | kaggle %>% 950 | filter(!`In which country do you currently reside?` %in% "Other") %>% 951 | count(`In which country do you currently reside?`) %>% 952 | rename(Country = `In which country do you currently reside?`) %>% 953 | arrange(desc(n)) %>% 954 | top_n(5) %>% 955 | ggplot() + geom_bar(aes(Country,n), stat = "identity") + 956 | coord_flip() + 957 | theme_minimal() + 958 | labs(title = "Top 5 Countries", 959 | subtitle = "From where Kaggle Survey Respondentns reside", 960 | x = "Country", 961 | y = "Number of Respondents", 962 | caption = "Data Source: Kaggle Survey 2018") 963 | ``` 964 | 965 | --- 966 | 967 | # Top 5 Countries - Plot #2 968 | 969 | ```{r countries2, echo=FALSE, message=FALSE, warning=FALSE} 970 | kaggle %>% 971 | filter(!`In which country do you currently reside?` %in% "Other") %>% 972 | count(`In which country do you currently reside?`) %>% 973 | rename(Country = `In which country do you currently reside?`) %>% 974 | arrange(desc(n)) %>% 975 | top_n(5) %>% 976 | ggplot() + geom_bar(aes(Country,n), stat = "identity") + 977 | coord_flip() + 978 | theme_minimal() + 979 | labs(title = "Top 5 Countries", 980 | subtitle = "From where Kaggle Survey Respondents reside", 981 | x = "Country", 982 | y = "Number of Respondents", 983 | caption = "Data Source: Kaggle Survey 2018") 984 | ``` 985 | 986 | --- 987 | 988 | # Top 5 Countries - Plot #3 Themed 989 | 990 | ```{r countries3, echo=FALSE, message=FALSE, warning=FALSE, fig.width= 10} 991 | library(ggthemes) 992 | 993 | kaggle %>% 994 | filter(!`In which country do you currently reside?` %in% "Other") %>% 995 | count(`In which country do you currently reside?`) %>% 996 | rename(Country = `In which country do you currently reside?`) %>% 997 | arrange(desc(n)) %>% 998 | top_n(5) %>% 999 | ggplot() + geom_bar(aes(Country,n), stat = "identity") + 1000 | # coord_flip() + 1001 | theme_minimal() + 1002 | labs(title = "Top 5 Countries", 1003 | subtitle = "From where Kaggle Survey Respondents reside", 1004 | x = "Country", 1005 | y = "Number of Respondents", 1006 | caption = "Data Source: Kaggle Survey 2018") + 1007 | ggthemes::theme_wsj() 1008 | ``` 1009 | 1010 | 1011 | --- 1012 | 1013 | class: inverse, center, middle 1014 | 1015 | # Documentation and Reporting using R Markdown 1016 | ## Demo 1017 | 1018 | --- 1019 | 1020 | class: inverse, center, middle 1021 | 1022 | # Project Demo 1023 | 1024 | --- 1025 | 1026 | 1027 | class: center, middle 1028 | 1029 | # Object Detection in 3 Lines of R Code 1030 | ## using Tiny YOLO 1031 | ### -Project Demo- 1032 | 1033 | --- 1034 | 1035 | 1036 | # References 1037 | 1038 | 1039 | - [R for Data Science](https://r4ds.had.co.nz/) 1040 | 1041 | - [R-Bloggers](https://www.r-bloggers.com/) 1042 | 1043 | --- 1044 | 1045 | class: center, middle 1046 | 1047 | # Thanks! 1048 | 1049 | Slides created via the R package [**xaringan**](https://github.com/yihui/xaringan). 1050 | 1051 | The chakra comes from [remark.js](https://remarkjs.com), [**knitr**](http://yihui.name/knitr), and [R Markdown](https://rmarkdown.rstudio.com). 1052 | 1053 | -------------------------------------------------------------------------------- /presentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrrs/r_beginners_workshop/1971ded361f43811a542583a43a0e82fd256642e/presentation.pdf -------------------------------------------------------------------------------- /presentation_files/figure-html/countries2 -1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrrs/r_beginners_workshop/1971ded361f43811a542583a43a0e82fd256642e/presentation_files/figure-html/countries2 -1.png -------------------------------------------------------------------------------- /presentation_files/figure-html/countries2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrrs/r_beginners_workshop/1971ded361f43811a542583a43a0e82fd256642e/presentation_files/figure-html/countries2-1.png -------------------------------------------------------------------------------- /presentation_files/figure-html/countries3 -1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrrs/r_beginners_workshop/1971ded361f43811a542583a43a0e82fd256642e/presentation_files/figure-html/countries3 -1.png -------------------------------------------------------------------------------- /presentation_files/figure-html/countries3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrrs/r_beginners_workshop/1971ded361f43811a542583a43a0e82fd256642e/presentation_files/figure-html/countries3-1.png -------------------------------------------------------------------------------- /presentation_files/figure-html/iechoFALSE-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrrs/r_beginners_workshop/1971ded361f43811a542583a43a0e82fd256642e/presentation_files/figure-html/iechoFALSE-1.png -------------------------------------------------------------------------------- /projects/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrrs/r_beginners_workshop/1971ded361f43811a542583a43a0e82fd256642e/projects/.DS_Store -------------------------------------------------------------------------------- /projects/Object_Detection_in_R/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /projects/Object_Detection_in_R/README.md: -------------------------------------------------------------------------------- 1 | # TinyYOLO in R 2 | 3 | Image Detection in R using `image.darknet` and Tiny YOLO 4 | 5 | # Input 6 | 7 | ![google-car.png](google-car.png) 8 | 9 | # References: 10 | 11 | * Project Link - [https://github.com/amrrs/tinyyolo_in_R](https://github.com/amrrs/tinyyolo_in_R) 12 | * Blogpost - [Object detection in just 3 lines of R code using Tiny YOLO](https://heartbeat.fritz.ai/object-detection-in-just-3-lines-of-r-code-using-tiny-yolo-b5a16e50e8a0) 13 | 14 | -------------------------------------------------------------------------------- /projects/Object_Detection_in_R/google-car.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrrs/r_beginners_workshop/1971ded361f43811a542583a43a0e82fd256642e/projects/Object_Detection_in_R/google-car.png -------------------------------------------------------------------------------- /projects/Object_Detection_in_R/tinyyolo_3_lines_in_R.R: -------------------------------------------------------------------------------- 1 | #devtools::install_github("bnosac/image", subdir = "image.darknet", build_vignettes = TRUE) 2 | 3 | library(image.darknet) 4 | 5 | #If required, Set new working directory where the final predictions imaged with bounding box will be saved 6 | 7 | setwd(paste0(getwd(),"/projects/")) 8 | 9 | #Define Model - here it is Tiny Yolo 10 | yolo_tiny_voc <- image_darknet_model(type = 'detect', 11 | model = "tiny-yolo-voc.cfg", 12 | weights = system.file(package="image.darknet", "models", "tiny-yolo-voc.weights"), 13 | labels = system.file(package="image.darknet", "include", "darknet", "data", "voc.names")) 14 | 15 | 16 | 17 | #Image Detection 18 | x <- image_darknet_detect(file = "Object_Detection_in_R/google-car.png", 19 | object = yolo_tiny_voc, 20 | threshold = 0.19) 21 | -------------------------------------------------------------------------------- /r_beginners_workshop.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | --------------------------------------------------------------------------------