├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── rollup.config.mjs ├── src ├── index.ts └── typings │ └── nerdamer.d.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | lib/ 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | .pnpm-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | node_modules/ 46 | jspm_packages/ 47 | 48 | # Snowpack dependency directory (https://snowpack.dev/) 49 | web_modules/ 50 | 51 | # TypeScript cache 52 | *.tsbuildinfo 53 | 54 | # Optional npm cache directory 55 | .npm 56 | 57 | # Optional eslint cache 58 | .eslintcache 59 | 60 | # Optional stylelint cache 61 | .stylelintcache 62 | 63 | # Microbundle cache 64 | .rpt2_cache/ 65 | .rts2_cache_cjs/ 66 | .rts2_cache_es/ 67 | .rts2_cache_umd/ 68 | 69 | # Optional REPL history 70 | .node_repl_history 71 | 72 | # Output of 'npm pack' 73 | *.tgz 74 | 75 | # Yarn Integrity file 76 | .yarn-integrity 77 | 78 | # dotenv environment variable files 79 | .env 80 | .env.development.local 81 | .env.test.local 82 | .env.production.local 83 | .env.local 84 | 85 | # parcel-bundler cache (https://parceljs.org/) 86 | .cache 87 | .parcel-cache 88 | 89 | # Next.js build output 90 | .next 91 | out 92 | 93 | # Nuxt.js build / generate output 94 | .nuxt 95 | dist 96 | 97 | # Gatsby files 98 | .cache/ 99 | # Comment in the public line in if your project uses Gatsby and not Next.js 100 | # https://nextjs.org/blog/next-9-1#public-directory-support 101 | # public 102 | 103 | # vuepress build output 104 | .vuepress/dist 105 | 106 | # vuepress v2.x temp and cache directory 107 | .temp 108 | .cache 109 | 110 | # Docusaurus cache and generated files 111 | .docusaurus 112 | 113 | # Serverless directories 114 | .serverless/ 115 | 116 | # FuseBox cache 117 | .fusebox/ 118 | 119 | # DynamoDB Local files 120 | .dynamodb/ 121 | 122 | # TernJS port file 123 | .tern-port 124 | 125 | # Stores VSCode versions used for testing VSCode extensions 126 | .vscode-test 127 | 128 | # yarn v2 129 | .yarn/cache 130 | .yarn/unplugged 131 | .yarn/build-state.yml 132 | .yarn/install-state.gz 133 | .pnp.* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Georgia Tech Visualization Lab 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BinGuru 2 | 3 | BinGuru is a Javascript package with an API to several established data binning / data classification methods, often used for visualizing data on choropleth maps. It also includes an implementation of a new, consensus binning method, 'Resiliency'. 4 | 5 | 6 | ### Install 7 | - `npm install binguru` 8 | 9 | 10 | ### Usage 11 | 12 | ```ts 13 | import { BinGuru } from "binguru"; 14 | 15 | let rawData = [1, 45, 65, 23, 65, 87, 54, 45, 31, 21, 12, 12, 98, 56, 76, null, null, "nan", undefined, "", "null"]; // Input array of numbers, strings, nulls, nans, undefineds. 16 | let binCount = 5; // Desired number of bins (inconsequential for certain binning methods, e.g., boxPlot). 17 | let binExtent = 10; // Desired bin interval (only applicable for certain binning methods, e.g., definedInterval). 18 | let precision = 2; // Desired rounding off precision. 19 | let binGuruObj = new BinGuru(rawData=rawData, binCount=binCount, binExtent=binExtent, precision=precision); // Initialize an instance of BinGuru 20 | 21 | let bins = binGuruObj.fisherJenks(); // Call an endpoint, e.g., fisherJenks() to bin using the FisherJenks / Natural Breaks binning method first. 22 | console.log(bins); 23 | ``` 24 | 25 | ### API and Demo 26 | Check out this Observable Notebook. 27 | 28 | 29 | ### Build and Publish 30 | - Install dependencies: `npm install` 31 | - Build the package: `npm run build` 32 | - Set version: `npm version prerelease --preid=` 33 | - Dry run: `npm publish --dry-run` 34 | - Publish to the registry: `npm publish` 35 | 36 | 37 | ### Credits 38 | BinGuru was created by 39 | Arpit Narechania, Alex Endert, and Clio Andris of the Georgia Tech Visualization Lab. We thank the members of the Georgia Tech Visualization Lab for their support and constructive feedback.

40 | 41 | 42 | ### Citations 43 | ```bibTeX 44 | @InProceedings{narechania2023resiliency, 45 | author = {Narechania, Arpit and Endert, Alex and Andris, Clio}, 46 | title = {{Resiliency: A Consensus Data Binning Method}}, 47 | booktitle = {12th International Conference on Geographic Information Science (GIScience 2023)}, 48 | pages = {55:1--55:7}, 49 | series = {Leibniz International Proceedings in Informatics (LIPIcs)}, 50 | year = {2023}, 51 | volume = {277}, 52 | publisher = {Schloss Dagstuhl -- Leibniz-Zentrum f{\"u}r Informatik}, 53 | doi = {10.4230/LIPIcs.GIScience.2023.55} 54 | } 55 | ``` 56 | 57 | ### License 58 | The software is available under the [MIT License](https://github.com/arpitnarechania/binguru/blob/master/LICENSE). 59 | 60 | 61 | ### Contact 62 | If you have any questions, feel free to [open an issue](https://github.com/arpitnarechania/binguru/issues/new/choose) or contact [Arpit Narechania](http://narechania.com). 63 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "binguru", 3 | "version": "1.0.0-alpha.18.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "binguru", 9 | "version": "1.0.0-alpha.18.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "nerdamer": "^1.1.13", 13 | "simple-statistics": "^7.8.3" 14 | }, 15 | "devDependencies": { 16 | "rimraf": "^5.0.1", 17 | "rollup": "^3.25.3", 18 | "typescript": "^5.1.5" 19 | }, 20 | "engines": { 21 | "node": ">=16.10.0", 22 | "npm": ">=7.24.0" 23 | } 24 | }, 25 | "node_modules/@isaacs/cliui": { 26 | "version": "8.0.2", 27 | "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", 28 | "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", 29 | "dev": true, 30 | "dependencies": { 31 | "string-width": "^5.1.2", 32 | "string-width-cjs": "npm:string-width@^4.2.0", 33 | "strip-ansi": "^7.0.1", 34 | "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", 35 | "wrap-ansi": "^8.1.0", 36 | "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" 37 | }, 38 | "engines": { 39 | "node": ">=12" 40 | } 41 | }, 42 | "node_modules/@pkgjs/parseargs": { 43 | "version": "0.11.0", 44 | "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", 45 | "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", 46 | "dev": true, 47 | "optional": true, 48 | "engines": { 49 | "node": ">=14" 50 | } 51 | }, 52 | "node_modules/ansi-regex": { 53 | "version": "6.0.1", 54 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", 55 | "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", 56 | "dev": true, 57 | "engines": { 58 | "node": ">=12" 59 | }, 60 | "funding": { 61 | "url": "https://github.com/chalk/ansi-regex?sponsor=1" 62 | } 63 | }, 64 | "node_modules/ansi-styles": { 65 | "version": "6.2.1", 66 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", 67 | "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", 68 | "dev": true, 69 | "engines": { 70 | "node": ">=12" 71 | }, 72 | "funding": { 73 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 74 | } 75 | }, 76 | "node_modules/balanced-match": { 77 | "version": "1.0.2", 78 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 79 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 80 | "dev": true 81 | }, 82 | "node_modules/brace-expansion": { 83 | "version": "2.0.1", 84 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 85 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 86 | "dev": true, 87 | "dependencies": { 88 | "balanced-match": "^1.0.0" 89 | } 90 | }, 91 | "node_modules/color-convert": { 92 | "version": "2.0.1", 93 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 94 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 95 | "dev": true, 96 | "dependencies": { 97 | "color-name": "~1.1.4" 98 | }, 99 | "engines": { 100 | "node": ">=7.0.0" 101 | } 102 | }, 103 | "node_modules/color-name": { 104 | "version": "1.1.4", 105 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 106 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 107 | "dev": true 108 | }, 109 | "node_modules/cross-spawn": { 110 | "version": "7.0.3", 111 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 112 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 113 | "dev": true, 114 | "dependencies": { 115 | "path-key": "^3.1.0", 116 | "shebang-command": "^2.0.0", 117 | "which": "^2.0.1" 118 | }, 119 | "engines": { 120 | "node": ">= 8" 121 | } 122 | }, 123 | "node_modules/eastasianwidth": { 124 | "version": "0.2.0", 125 | "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", 126 | "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", 127 | "dev": true 128 | }, 129 | "node_modules/emoji-regex": { 130 | "version": "9.2.2", 131 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", 132 | "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", 133 | "dev": true 134 | }, 135 | "node_modules/foreground-child": { 136 | "version": "3.1.1", 137 | "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", 138 | "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", 139 | "dev": true, 140 | "dependencies": { 141 | "cross-spawn": "^7.0.0", 142 | "signal-exit": "^4.0.1" 143 | }, 144 | "engines": { 145 | "node": ">=14" 146 | }, 147 | "funding": { 148 | "url": "https://github.com/sponsors/isaacs" 149 | } 150 | }, 151 | "node_modules/fsevents": { 152 | "version": "2.3.2", 153 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 154 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 155 | "dev": true, 156 | "hasInstallScript": true, 157 | "optional": true, 158 | "os": [ 159 | "darwin" 160 | ], 161 | "engines": { 162 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 163 | } 164 | }, 165 | "node_modules/is-fullwidth-code-point": { 166 | "version": "3.0.0", 167 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 168 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 169 | "dev": true, 170 | "engines": { 171 | "node": ">=8" 172 | } 173 | }, 174 | "node_modules/isexe": { 175 | "version": "2.0.0", 176 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 177 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 178 | "dev": true 179 | }, 180 | "node_modules/jackspeak": { 181 | "version": "2.2.1", 182 | "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.2.1.tgz", 183 | "integrity": "sha512-MXbxovZ/Pm42f6cDIDkl3xpwv1AGwObKwfmjs2nQePiy85tP3fatofl3FC1aBsOtP/6fq5SbtgHwWcMsLP+bDw==", 184 | "dev": true, 185 | "dependencies": { 186 | "@isaacs/cliui": "^8.0.2" 187 | }, 188 | "engines": { 189 | "node": ">=14" 190 | }, 191 | "funding": { 192 | "url": "https://github.com/sponsors/isaacs" 193 | }, 194 | "optionalDependencies": { 195 | "@pkgjs/parseargs": "^0.11.0" 196 | } 197 | }, 198 | "node_modules/lru-cache": { 199 | "version": "10.0.0", 200 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.0.tgz", 201 | "integrity": "sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw==", 202 | "dev": true, 203 | "engines": { 204 | "node": "14 || >=16.14" 205 | } 206 | }, 207 | "node_modules/minipass": { 208 | "version": "6.0.2", 209 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-6.0.2.tgz", 210 | "integrity": "sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==", 211 | "dev": true, 212 | "engines": { 213 | "node": ">=16 || 14 >=14.17" 214 | } 215 | }, 216 | "node_modules/nerdamer": { 217 | "version": "1.1.13", 218 | "resolved": "https://registry.npmjs.org/nerdamer/-/nerdamer-1.1.13.tgz", 219 | "integrity": "sha512-kQGQYd42eQpKDOnU8ZnRKF47c+gK6jVC46eUchrABsovtFruHvsjyjBO32jck8QnpZE5z5R8HQw72hQX9Oq2MQ==", 220 | "engines": { 221 | "node": ">=0.10.0" 222 | } 223 | }, 224 | "node_modules/path-key": { 225 | "version": "3.1.1", 226 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 227 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 228 | "dev": true, 229 | "engines": { 230 | "node": ">=8" 231 | } 232 | }, 233 | "node_modules/path-scurry": { 234 | "version": "1.10.0", 235 | "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.0.tgz", 236 | "integrity": "sha512-tZFEaRQbMLjwrsmidsGJ6wDMv0iazJWk6SfIKnY4Xru8auXgmJkOBa5DUbYFcFD2Rzk2+KDlIiF0GVXNCbgC7g==", 237 | "dev": true, 238 | "dependencies": { 239 | "lru-cache": "^9.1.1 || ^10.0.0", 240 | "minipass": "^5.0.0 || ^6.0.2" 241 | }, 242 | "engines": { 243 | "node": ">=16 || 14 >=14.17" 244 | }, 245 | "funding": { 246 | "url": "https://github.com/sponsors/isaacs" 247 | } 248 | }, 249 | "node_modules/rimraf": { 250 | "version": "5.0.1", 251 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.1.tgz", 252 | "integrity": "sha512-OfFZdwtd3lZ+XZzYP/6gTACubwFcHdLRqS9UX3UwpU2dnGQYkPFISRwvM3w9IiB2w7bW5qGo/uAwE4SmXXSKvg==", 253 | "dev": true, 254 | "dependencies": { 255 | "glob": "^10.2.5" 256 | }, 257 | "bin": { 258 | "rimraf": "dist/cjs/src/bin.js" 259 | }, 260 | "engines": { 261 | "node": ">=14" 262 | }, 263 | "funding": { 264 | "url": "https://github.com/sponsors/isaacs" 265 | } 266 | }, 267 | "node_modules/rimraf/node_modules/glob": { 268 | "version": "10.3.1", 269 | "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.1.tgz", 270 | "integrity": "sha512-9BKYcEeIs7QwlCYs+Y3GBvqAMISufUS0i2ELd11zpZjxI5V9iyRj0HgzB5/cLf2NY4vcYBTYzJ7GIui7j/4DOw==", 271 | "dev": true, 272 | "dependencies": { 273 | "foreground-child": "^3.1.0", 274 | "jackspeak": "^2.0.3", 275 | "minimatch": "^9.0.1", 276 | "minipass": "^5.0.0 || ^6.0.2", 277 | "path-scurry": "^1.10.0" 278 | }, 279 | "bin": { 280 | "glob": "dist/cjs/src/bin.js" 281 | }, 282 | "engines": { 283 | "node": ">=16 || 14 >=14.17" 284 | }, 285 | "funding": { 286 | "url": "https://github.com/sponsors/isaacs" 287 | } 288 | }, 289 | "node_modules/rimraf/node_modules/minimatch": { 290 | "version": "9.0.2", 291 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.2.tgz", 292 | "integrity": "sha512-PZOT9g5v2ojiTL7r1xF6plNHLtOeTpSlDI007As2NlA2aYBMfVom17yqa6QzhmDP8QOhn7LjHTg7DFCVSSa6yg==", 293 | "dev": true, 294 | "dependencies": { 295 | "brace-expansion": "^2.0.1" 296 | }, 297 | "engines": { 298 | "node": ">=16 || 14 >=14.17" 299 | }, 300 | "funding": { 301 | "url": "https://github.com/sponsors/isaacs" 302 | } 303 | }, 304 | "node_modules/rollup": { 305 | "version": "3.25.3", 306 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.3.tgz", 307 | "integrity": "sha512-ZT279hx8gszBj9uy5FfhoG4bZx8c+0A1sbqtr7Q3KNWIizpTdDEPZbV2xcbvHsnFp4MavCQYZyzApJ+virB8Yw==", 308 | "dev": true, 309 | "bin": { 310 | "rollup": "dist/bin/rollup" 311 | }, 312 | "engines": { 313 | "node": ">=14.18.0", 314 | "npm": ">=8.0.0" 315 | }, 316 | "optionalDependencies": { 317 | "fsevents": "~2.3.2" 318 | } 319 | }, 320 | "node_modules/shebang-command": { 321 | "version": "2.0.0", 322 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 323 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 324 | "dev": true, 325 | "dependencies": { 326 | "shebang-regex": "^3.0.0" 327 | }, 328 | "engines": { 329 | "node": ">=8" 330 | } 331 | }, 332 | "node_modules/shebang-regex": { 333 | "version": "3.0.0", 334 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 335 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 336 | "dev": true, 337 | "engines": { 338 | "node": ">=8" 339 | } 340 | }, 341 | "node_modules/signal-exit": { 342 | "version": "4.0.2", 343 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz", 344 | "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==", 345 | "dev": true, 346 | "engines": { 347 | "node": ">=14" 348 | }, 349 | "funding": { 350 | "url": "https://github.com/sponsors/isaacs" 351 | } 352 | }, 353 | "node_modules/simple-statistics": { 354 | "version": "7.8.3", 355 | "resolved": "https://registry.npmjs.org/simple-statistics/-/simple-statistics-7.8.3.tgz", 356 | "integrity": "sha512-JFvMY00t6SBGtwMuJ+nqgsx9ylkMiJ5JlK9bkj8AdvniIe5615wWQYkKHXe84XtSuc40G/tlrPu0A5/NlJvv8A==", 357 | "engines": { 358 | "node": "*" 359 | } 360 | }, 361 | "node_modules/string-width": { 362 | "version": "5.1.2", 363 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", 364 | "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", 365 | "dev": true, 366 | "dependencies": { 367 | "eastasianwidth": "^0.2.0", 368 | "emoji-regex": "^9.2.2", 369 | "strip-ansi": "^7.0.1" 370 | }, 371 | "engines": { 372 | "node": ">=12" 373 | }, 374 | "funding": { 375 | "url": "https://github.com/sponsors/sindresorhus" 376 | } 377 | }, 378 | "node_modules/string-width-cjs": { 379 | "name": "string-width", 380 | "version": "4.2.3", 381 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 382 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 383 | "dev": true, 384 | "dependencies": { 385 | "emoji-regex": "^8.0.0", 386 | "is-fullwidth-code-point": "^3.0.0", 387 | "strip-ansi": "^6.0.1" 388 | }, 389 | "engines": { 390 | "node": ">=8" 391 | } 392 | }, 393 | "node_modules/string-width-cjs/node_modules/ansi-regex": { 394 | "version": "5.0.1", 395 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 396 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 397 | "dev": true, 398 | "engines": { 399 | "node": ">=8" 400 | } 401 | }, 402 | "node_modules/string-width-cjs/node_modules/emoji-regex": { 403 | "version": "8.0.0", 404 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 405 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 406 | "dev": true 407 | }, 408 | "node_modules/string-width-cjs/node_modules/strip-ansi": { 409 | "version": "6.0.1", 410 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 411 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 412 | "dev": true, 413 | "dependencies": { 414 | "ansi-regex": "^5.0.1" 415 | }, 416 | "engines": { 417 | "node": ">=8" 418 | } 419 | }, 420 | "node_modules/strip-ansi": { 421 | "version": "7.1.0", 422 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", 423 | "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", 424 | "dev": true, 425 | "dependencies": { 426 | "ansi-regex": "^6.0.1" 427 | }, 428 | "engines": { 429 | "node": ">=12" 430 | }, 431 | "funding": { 432 | "url": "https://github.com/chalk/strip-ansi?sponsor=1" 433 | } 434 | }, 435 | "node_modules/strip-ansi-cjs": { 436 | "name": "strip-ansi", 437 | "version": "6.0.1", 438 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 439 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 440 | "dev": true, 441 | "dependencies": { 442 | "ansi-regex": "^5.0.1" 443 | }, 444 | "engines": { 445 | "node": ">=8" 446 | } 447 | }, 448 | "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { 449 | "version": "5.0.1", 450 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 451 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 452 | "dev": true, 453 | "engines": { 454 | "node": ">=8" 455 | } 456 | }, 457 | "node_modules/typescript": { 458 | "version": "5.1.5", 459 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.5.tgz", 460 | "integrity": "sha512-FOH+WN/DQjUvN6WgW+c4Ml3yi0PH+a/8q+kNIfRehv1wLhWONedw85iu+vQ39Wp49IzTJEsZ2lyLXpBF7mkF1g==", 461 | "dev": true, 462 | "bin": { 463 | "tsc": "bin/tsc", 464 | "tsserver": "bin/tsserver" 465 | }, 466 | "engines": { 467 | "node": ">=14.17" 468 | } 469 | }, 470 | "node_modules/which": { 471 | "version": "2.0.2", 472 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 473 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 474 | "dev": true, 475 | "dependencies": { 476 | "isexe": "^2.0.0" 477 | }, 478 | "bin": { 479 | "node-which": "bin/node-which" 480 | }, 481 | "engines": { 482 | "node": ">= 8" 483 | } 484 | }, 485 | "node_modules/wrap-ansi": { 486 | "version": "8.1.0", 487 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", 488 | "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", 489 | "dev": true, 490 | "dependencies": { 491 | "ansi-styles": "^6.1.0", 492 | "string-width": "^5.0.1", 493 | "strip-ansi": "^7.0.1" 494 | }, 495 | "engines": { 496 | "node": ">=12" 497 | }, 498 | "funding": { 499 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 500 | } 501 | }, 502 | "node_modules/wrap-ansi-cjs": { 503 | "name": "wrap-ansi", 504 | "version": "7.0.0", 505 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 506 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 507 | "dev": true, 508 | "dependencies": { 509 | "ansi-styles": "^4.0.0", 510 | "string-width": "^4.1.0", 511 | "strip-ansi": "^6.0.0" 512 | }, 513 | "engines": { 514 | "node": ">=10" 515 | }, 516 | "funding": { 517 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 518 | } 519 | }, 520 | "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { 521 | "version": "5.0.1", 522 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 523 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 524 | "dev": true, 525 | "engines": { 526 | "node": ">=8" 527 | } 528 | }, 529 | "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { 530 | "version": "4.3.0", 531 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 532 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 533 | "dev": true, 534 | "dependencies": { 535 | "color-convert": "^2.0.1" 536 | }, 537 | "engines": { 538 | "node": ">=8" 539 | }, 540 | "funding": { 541 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 542 | } 543 | }, 544 | "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { 545 | "version": "8.0.0", 546 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 547 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 548 | "dev": true 549 | }, 550 | "node_modules/wrap-ansi-cjs/node_modules/string-width": { 551 | "version": "4.2.3", 552 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 553 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 554 | "dev": true, 555 | "dependencies": { 556 | "emoji-regex": "^8.0.0", 557 | "is-fullwidth-code-point": "^3.0.0", 558 | "strip-ansi": "^6.0.1" 559 | }, 560 | "engines": { 561 | "node": ">=8" 562 | } 563 | }, 564 | "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { 565 | "version": "6.0.1", 566 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 567 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 568 | "dev": true, 569 | "dependencies": { 570 | "ansi-regex": "^5.0.1" 571 | }, 572 | "engines": { 573 | "node": ">=8" 574 | } 575 | } 576 | }, 577 | "dependencies": { 578 | "@isaacs/cliui": { 579 | "version": "8.0.2", 580 | "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", 581 | "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", 582 | "dev": true, 583 | "requires": { 584 | "string-width": "^5.1.2", 585 | "string-width-cjs": "npm:string-width@^4.2.0", 586 | "strip-ansi": "^7.0.1", 587 | "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", 588 | "wrap-ansi": "^8.1.0", 589 | "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" 590 | } 591 | }, 592 | "@pkgjs/parseargs": { 593 | "version": "0.11.0", 594 | "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", 595 | "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", 596 | "dev": true, 597 | "optional": true 598 | }, 599 | "ansi-regex": { 600 | "version": "6.0.1", 601 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", 602 | "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", 603 | "dev": true 604 | }, 605 | "ansi-styles": { 606 | "version": "6.2.1", 607 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", 608 | "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", 609 | "dev": true 610 | }, 611 | "balanced-match": { 612 | "version": "1.0.2", 613 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 614 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 615 | "dev": true 616 | }, 617 | "brace-expansion": { 618 | "version": "2.0.1", 619 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 620 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 621 | "dev": true, 622 | "requires": { 623 | "balanced-match": "^1.0.0" 624 | } 625 | }, 626 | "color-convert": { 627 | "version": "2.0.1", 628 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 629 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 630 | "dev": true, 631 | "requires": { 632 | "color-name": "~1.1.4" 633 | } 634 | }, 635 | "color-name": { 636 | "version": "1.1.4", 637 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 638 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 639 | "dev": true 640 | }, 641 | "cross-spawn": { 642 | "version": "7.0.3", 643 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 644 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 645 | "dev": true, 646 | "requires": { 647 | "path-key": "^3.1.0", 648 | "shebang-command": "^2.0.0", 649 | "which": "^2.0.1" 650 | } 651 | }, 652 | "eastasianwidth": { 653 | "version": "0.2.0", 654 | "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", 655 | "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", 656 | "dev": true 657 | }, 658 | "emoji-regex": { 659 | "version": "9.2.2", 660 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", 661 | "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", 662 | "dev": true 663 | }, 664 | "foreground-child": { 665 | "version": "3.1.1", 666 | "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", 667 | "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", 668 | "dev": true, 669 | "requires": { 670 | "cross-spawn": "^7.0.0", 671 | "signal-exit": "^4.0.1" 672 | } 673 | }, 674 | "fsevents": { 675 | "version": "2.3.2", 676 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 677 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 678 | "dev": true, 679 | "optional": true 680 | }, 681 | "is-fullwidth-code-point": { 682 | "version": "3.0.0", 683 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 684 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 685 | "dev": true 686 | }, 687 | "isexe": { 688 | "version": "2.0.0", 689 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 690 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 691 | "dev": true 692 | }, 693 | "jackspeak": { 694 | "version": "2.2.1", 695 | "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.2.1.tgz", 696 | "integrity": "sha512-MXbxovZ/Pm42f6cDIDkl3xpwv1AGwObKwfmjs2nQePiy85tP3fatofl3FC1aBsOtP/6fq5SbtgHwWcMsLP+bDw==", 697 | "dev": true, 698 | "requires": { 699 | "@isaacs/cliui": "^8.0.2", 700 | "@pkgjs/parseargs": "^0.11.0" 701 | } 702 | }, 703 | "lru-cache": { 704 | "version": "10.0.0", 705 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.0.tgz", 706 | "integrity": "sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw==", 707 | "dev": true 708 | }, 709 | "minipass": { 710 | "version": "6.0.2", 711 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-6.0.2.tgz", 712 | "integrity": "sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==", 713 | "dev": true 714 | }, 715 | "nerdamer": { 716 | "version": "1.1.13", 717 | "resolved": "https://registry.npmjs.org/nerdamer/-/nerdamer-1.1.13.tgz", 718 | "integrity": "sha512-kQGQYd42eQpKDOnU8ZnRKF47c+gK6jVC46eUchrABsovtFruHvsjyjBO32jck8QnpZE5z5R8HQw72hQX9Oq2MQ==" 719 | }, 720 | "path-key": { 721 | "version": "3.1.1", 722 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 723 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 724 | "dev": true 725 | }, 726 | "path-scurry": { 727 | "version": "1.10.0", 728 | "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.0.tgz", 729 | "integrity": "sha512-tZFEaRQbMLjwrsmidsGJ6wDMv0iazJWk6SfIKnY4Xru8auXgmJkOBa5DUbYFcFD2Rzk2+KDlIiF0GVXNCbgC7g==", 730 | "dev": true, 731 | "requires": { 732 | "lru-cache": "^9.1.1 || ^10.0.0", 733 | "minipass": "^5.0.0 || ^6.0.2" 734 | } 735 | }, 736 | "rimraf": { 737 | "version": "5.0.1", 738 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.1.tgz", 739 | "integrity": "sha512-OfFZdwtd3lZ+XZzYP/6gTACubwFcHdLRqS9UX3UwpU2dnGQYkPFISRwvM3w9IiB2w7bW5qGo/uAwE4SmXXSKvg==", 740 | "dev": true, 741 | "requires": { 742 | "glob": "^10.2.5" 743 | }, 744 | "dependencies": { 745 | "glob": { 746 | "version": "10.3.1", 747 | "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.1.tgz", 748 | "integrity": "sha512-9BKYcEeIs7QwlCYs+Y3GBvqAMISufUS0i2ELd11zpZjxI5V9iyRj0HgzB5/cLf2NY4vcYBTYzJ7GIui7j/4DOw==", 749 | "dev": true, 750 | "requires": { 751 | "foreground-child": "^3.1.0", 752 | "jackspeak": "^2.0.3", 753 | "minimatch": "^9.0.1", 754 | "minipass": "^5.0.0 || ^6.0.2", 755 | "path-scurry": "^1.10.0" 756 | } 757 | }, 758 | "minimatch": { 759 | "version": "9.0.2", 760 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.2.tgz", 761 | "integrity": "sha512-PZOT9g5v2ojiTL7r1xF6plNHLtOeTpSlDI007As2NlA2aYBMfVom17yqa6QzhmDP8QOhn7LjHTg7DFCVSSa6yg==", 762 | "dev": true, 763 | "requires": { 764 | "brace-expansion": "^2.0.1" 765 | } 766 | } 767 | } 768 | }, 769 | "rollup": { 770 | "version": "3.25.3", 771 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.3.tgz", 772 | "integrity": "sha512-ZT279hx8gszBj9uy5FfhoG4bZx8c+0A1sbqtr7Q3KNWIizpTdDEPZbV2xcbvHsnFp4MavCQYZyzApJ+virB8Yw==", 773 | "dev": true, 774 | "requires": { 775 | "fsevents": "~2.3.2" 776 | } 777 | }, 778 | "shebang-command": { 779 | "version": "2.0.0", 780 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 781 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 782 | "dev": true, 783 | "requires": { 784 | "shebang-regex": "^3.0.0" 785 | } 786 | }, 787 | "shebang-regex": { 788 | "version": "3.0.0", 789 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 790 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 791 | "dev": true 792 | }, 793 | "signal-exit": { 794 | "version": "4.0.2", 795 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz", 796 | "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==", 797 | "dev": true 798 | }, 799 | "simple-statistics": { 800 | "version": "7.8.3", 801 | "resolved": "https://registry.npmjs.org/simple-statistics/-/simple-statistics-7.8.3.tgz", 802 | "integrity": "sha512-JFvMY00t6SBGtwMuJ+nqgsx9ylkMiJ5JlK9bkj8AdvniIe5615wWQYkKHXe84XtSuc40G/tlrPu0A5/NlJvv8A==" 803 | }, 804 | "string-width": { 805 | "version": "5.1.2", 806 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", 807 | "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", 808 | "dev": true, 809 | "requires": { 810 | "eastasianwidth": "^0.2.0", 811 | "emoji-regex": "^9.2.2", 812 | "strip-ansi": "^7.0.1" 813 | } 814 | }, 815 | "string-width-cjs": { 816 | "version": "npm:string-width@4.2.3", 817 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 818 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 819 | "dev": true, 820 | "requires": { 821 | "emoji-regex": "^8.0.0", 822 | "is-fullwidth-code-point": "^3.0.0", 823 | "strip-ansi": "^6.0.1" 824 | }, 825 | "dependencies": { 826 | "ansi-regex": { 827 | "version": "5.0.1", 828 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 829 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 830 | "dev": true 831 | }, 832 | "emoji-regex": { 833 | "version": "8.0.0", 834 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 835 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 836 | "dev": true 837 | }, 838 | "strip-ansi": { 839 | "version": "6.0.1", 840 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 841 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 842 | "dev": true, 843 | "requires": { 844 | "ansi-regex": "^5.0.1" 845 | } 846 | } 847 | } 848 | }, 849 | "strip-ansi": { 850 | "version": "7.1.0", 851 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", 852 | "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", 853 | "dev": true, 854 | "requires": { 855 | "ansi-regex": "^6.0.1" 856 | } 857 | }, 858 | "strip-ansi-cjs": { 859 | "version": "npm:strip-ansi@6.0.1", 860 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 861 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 862 | "dev": true, 863 | "requires": { 864 | "ansi-regex": "^5.0.1" 865 | }, 866 | "dependencies": { 867 | "ansi-regex": { 868 | "version": "5.0.1", 869 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 870 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 871 | "dev": true 872 | } 873 | } 874 | }, 875 | "typescript": { 876 | "version": "5.1.5", 877 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.5.tgz", 878 | "integrity": "sha512-FOH+WN/DQjUvN6WgW+c4Ml3yi0PH+a/8q+kNIfRehv1wLhWONedw85iu+vQ39Wp49IzTJEsZ2lyLXpBF7mkF1g==", 879 | "dev": true 880 | }, 881 | "which": { 882 | "version": "2.0.2", 883 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 884 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 885 | "dev": true, 886 | "requires": { 887 | "isexe": "^2.0.0" 888 | } 889 | }, 890 | "wrap-ansi": { 891 | "version": "8.1.0", 892 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", 893 | "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", 894 | "dev": true, 895 | "requires": { 896 | "ansi-styles": "^6.1.0", 897 | "string-width": "^5.0.1", 898 | "strip-ansi": "^7.0.1" 899 | } 900 | }, 901 | "wrap-ansi-cjs": { 902 | "version": "npm:wrap-ansi@7.0.0", 903 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 904 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 905 | "dev": true, 906 | "requires": { 907 | "ansi-styles": "^4.0.0", 908 | "string-width": "^4.1.0", 909 | "strip-ansi": "^6.0.0" 910 | }, 911 | "dependencies": { 912 | "ansi-regex": { 913 | "version": "5.0.1", 914 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 915 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 916 | "dev": true 917 | }, 918 | "ansi-styles": { 919 | "version": "4.3.0", 920 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 921 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 922 | "dev": true, 923 | "requires": { 924 | "color-convert": "^2.0.1" 925 | } 926 | }, 927 | "emoji-regex": { 928 | "version": "8.0.0", 929 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 930 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 931 | "dev": true 932 | }, 933 | "string-width": { 934 | "version": "4.2.3", 935 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 936 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 937 | "dev": true, 938 | "requires": { 939 | "emoji-regex": "^8.0.0", 940 | "is-fullwidth-code-point": "^3.0.0", 941 | "strip-ansi": "^6.0.1" 942 | } 943 | }, 944 | "strip-ansi": { 945 | "version": "6.0.1", 946 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 947 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 948 | "dev": true, 949 | "requires": { 950 | "ansi-regex": "^5.0.1" 951 | } 952 | } 953 | } 954 | } 955 | } 956 | } 957 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "binguru", 3 | "version": "1.0.0-alpha.18.0", 4 | "description": "BinGuru is a Javascript package with an API to several established data binning / data classification methods, often used for visualizing data on choropleth maps. It also includes an implementation of a new, consensus binning method, 'Resiliency'.", 5 | "main": "dist/index.mjs", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "rimraf dist && tsc && rollup -c rollup.config.mjs" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/arpitnarechania/binguru.git" 13 | }, 14 | "dependencies": { 15 | "nerdamer": "^1.1.13", 16 | "simple-statistics": "^7.8.3" 17 | }, 18 | "keywords": [ 19 | "data", 20 | "binning;", 21 | "data", 22 | "classification", 23 | "choropleth", 24 | "maps", 25 | "GIS", 26 | "cartography", 27 | "visualization" 28 | ], 29 | "author": "Arpit Narechania, Alex Endert, Clio Andris", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/arpitnarechania/binguru/issues" 33 | }, 34 | "homepage": "https://github.com/arpitnarechania/binguru#readme", 35 | "devDependencies": { 36 | "rimraf": "^5.0.1", 37 | "rollup": "^3.25.3", 38 | "typescript": "^5.1.5" 39 | }, 40 | "engines": { 41 | "npm": ">=7.24.0", 42 | "node": ">=16.10.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | input: 'lib/index.js', 3 | output: [ 4 | { 5 | file: 'dist/index.mjs', 6 | format: 'es', 7 | }, 8 | { 9 | file: 'dist/index.umd.js', 10 | format: 'umd', 11 | name: 'binguru' 12 | }, 13 | { 14 | file: 'dist/index.amd.js', 15 | format: 'amd' 16 | }, 17 | { 18 | file: 'dist/index.cjs', 19 | format: 'cjs' 20 | } 21 | ] 22 | }; -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | // Filename: index.ts 3 | // Purpose: Main entrypoint for the BinGuru Javascript library. 4 | **********************************************************/ 5 | 6 | 7 | import * as ss from 'simple-statistics'; 8 | import nerdamer from 'nerdamer/nerdamer.core.js'; 9 | import 'nerdamer/Algebra.js'; 10 | import 'nerdamer/Calculus.js'; 11 | import 'nerdamer/Solve.js'; 12 | 13 | 14 | export const EQUAL_INTERVAL = "equalInterval"; 15 | export const PERCENTILE = "percentile"; 16 | export const DEFINED_INTERVAL = "definedInterval"; 17 | export const QUANTILE = "quantile"; 18 | export const BOXPLOT = "boxPlot"; 19 | export const STANDARD_DEVIATION = "standardDeviation"; 20 | export const MAXIMUM_BREAKS = "maximumBreaks"; 21 | export const PRETTY_BREAKS = "prettyBreaks"; 22 | export const CK_MEANS = "ckMeans"; 23 | export const HEAD_TAIL_BREAKS = "headTailBreaks"; 24 | export const FISHER_JENKS = "fisherJenks"; 25 | export const EXPONENTIAL_BIN_SIZE = "exponentialBinSizes"; 26 | export const GEOMETRIC_INTERVAL = "geometricInterval"; 27 | export const UNCLASSED = "unclassed"; 28 | export const UNIQUE = "unique"; 29 | export const MANUAL_INTERVAL = "manualInterval"; 30 | export const RESILIENCY = "resiliency"; 31 | 32 | export class BinGuru { 33 | 34 | rawData: any[]; 35 | binCount: number; 36 | binExtent: number; 37 | precision: number; 38 | 39 | data: number[]; 40 | minSortedData: number[]; 41 | maxSortedData: number[]; 42 | 43 | median: number; 44 | mean: number; 45 | sd: number; 46 | iqr: number; 47 | lq1: number; 48 | lq10: number; 49 | lq25: number; 50 | uq75: number; 51 | uq90: number; 52 | uq99: number; 53 | min: number; 54 | max: number; 55 | nonZeroMin: number; 56 | 57 | visModel = {}; 58 | 59 | constructor(rawData = [], binCount = 5, binExtent = 10, precision = 2) { 60 | 61 | // Set input params 62 | this.rawData = rawData; 63 | this.binCount = binCount; 64 | this.binExtent = binExtent; 65 | this.precision = precision; 66 | 67 | // Process Data 68 | this.data = this.rawData.filter(value => this.isValid(value)); // only work with non NaN, non null, non undefined, numeric data 69 | this.minSortedData = JSON.parse(JSON.stringify(this.data)).sort((n1: number, n2: number) => n1 - n2); 70 | this.maxSortedData = JSON.parse(JSON.stringify(this.data)).sort((n1: number, n2: number) => n2 - n1); 71 | 72 | // Compute Basic Stats 73 | this.median = ss.median(this.data); 74 | this.mean = ss.mean(this.data); 75 | this.sd = ss.standardDeviation(this.data); 76 | this.iqr = ss.interquartileRange(this.data); 77 | this.lq1 = ss.quantile(this.data, 0.01); 78 | this.lq10 = ss.quantile(this.data, 0.10); 79 | this.lq25 = ss.quantile(this.data, 0.25); 80 | this.uq75 = ss.quantile(this.data, 0.75); 81 | this.uq90 = ss.quantile(this.data, 0.90); 82 | this.uq99 = ss.quantile(this.data, 0.99); 83 | [this.min, this.max] = ss.extent(this.data) 84 | this.nonZeroMin = Math.min.apply(null, this.data.filter(Boolean)); 85 | 86 | // Round off everything to 2 digits. 87 | this.median = parseFloat(this.median.toFixed(2)); 88 | this.mean = parseFloat(this.mean.toFixed(2)); 89 | this.sd = parseFloat(this.sd.toFixed(2)); 90 | this.iqr = parseFloat(this.iqr.toFixed(2)); 91 | this.lq1 = parseFloat(this.lq1.toFixed(2)); 92 | this.lq10 = parseFloat(this.lq10.toFixed(2)); 93 | this.lq25 = parseFloat(this.lq25.toFixed(2)); 94 | this.uq75 = parseFloat(this.uq75.toFixed(2)); 95 | this.uq90 = parseFloat(this.uq90.toFixed(2)); 96 | this.uq99 = parseFloat(this.uq99.toFixed(2)); 97 | [this.min, this.max] = [this.min, this.max].map((item) => parseFloat(item.toFixed(2))); 98 | this.nonZeroMin = parseFloat(this.nonZeroMin.toFixed(2)); 99 | } 100 | 101 | /** 102 | * Return most frequently occurring element in the array. 103 | */ 104 | getMostFrequentElement(array: number[]) { 105 | const store: any = {}; 106 | array.forEach((num: number) => store[num] ? store[num] += 1 : store[num] = 1); 107 | return parseInt(Object.keys(store).sort((a, b) => store[b] - store[a])[0]); 108 | } 109 | 110 | /** 111 | * Return frequency of most frequently occurring element in the array. 112 | */ 113 | getFrequencyOfMostFrequentElement(array: number[]) { 114 | var mp = new Map(); 115 | var n = array.length; 116 | 117 | // Traverse through array elements and 118 | // count frequencies 119 | for (var i = 0; i < n; i++) { 120 | if (mp.has(array[i])) 121 | mp.set(array[i], mp.get(array[i]) + 1) 122 | else 123 | mp.set(array[i], 1) 124 | } 125 | 126 | var keys: any = []; 127 | mp.forEach((value, key) => { 128 | keys.push(key); 129 | }); 130 | keys.sort((a: number, b: number) => a - b); 131 | 132 | // Traverse through map and print frequencies 133 | let max = -Infinity; 134 | keys.forEach((key: string) => { 135 | let val = mp.get(key); 136 | if (val > max) { 137 | max = val; 138 | } 139 | }); 140 | 141 | return max; 142 | } 143 | 144 | 145 | /** 146 | * Maximum Breaks 147 | * @returns { binCount: number, binBreaks: number[], binSizes: object, dataRange: number[], dataBinAssignments: object } 148 | */ 149 | maximumBreaks() { 150 | let context = this; 151 | let binBreaks: number[] = []; 152 | 153 | // compute the differences between adjacent array elements in the sorted version of the data. 154 | let diffs: number[] = []; 155 | for (var i = 0; i < context.minSortedData.length - 1; i++) { 156 | const diff = context.minSortedData[i + 1] - context.minSortedData[i]; 157 | diffs.push(diff); 158 | } 159 | 160 | // note the corresponding indices of the element diffs that is sorted in the descending order by their diff. 161 | var len = diffs.length; 162 | var indices = new Array(len); 163 | for (var i = 0; i < len; ++i) indices[i] = i; 164 | indices.sort(function (a, b) { return diffs[a] < diffs[b] ? 1 : diffs[a] > diffs[b] ? -1 : 0; }); // descending order 165 | 166 | // Next, choose the top `noOfBreaks` (or binCount-1) 167 | // Note: do index + 1 - because `threshold` scale binBreaks has < upper limit and not <= upper limit. 168 | for (let i = 0; i < (context.binCount - 1); i++) { 169 | binBreaks.push(context.minSortedData[indices[i] + 1]); 170 | } 171 | binBreaks = binBreaks.sort(function (a, b) { return a - b; }); // Need to sort it back to ascending order; 172 | 173 | // Compute Bin Sizes 174 | let binSizes = context.computeBinSizes(binBreaks); 175 | 176 | // Compute Data-> Bin Assignments 177 | let dataBinAssignments = context.computeDataBinAssignments(binBreaks); 178 | 179 | // Return final Bin Object 180 | return { 181 | "rawData": context.rawData, 182 | "data": context.data, 183 | "dataRange": [context.min, context.max], 184 | "binCount": context.binCount, 185 | "binBreaks": context.roundToPrecision(binBreaks, context.precision), 186 | "binSizes": binSizes, 187 | "dataBinAssignments": dataBinAssignments 188 | } 189 | 190 | } 191 | 192 | /** 193 | * Head Tail Breaks 194 | * @returns { binCount: number, binBreaks: number[], binSizes: object, dataRange: number[], dataBinAssignments: object } 195 | */ 196 | headTailBreaks() { 197 | let context = this; 198 | let binBreaks: number[] = []; 199 | 200 | function recursive(data: number[]) { 201 | let data_mean: number = data.reduce(function (a, b) { return a + b }) / data.length; 202 | let head = data.filter(function (d) { return d > data_mean }); 203 | binBreaks.push(data_mean); 204 | 205 | while (head.length > 1 && head.length / data.length < 0.40) { 206 | return recursive(head); 207 | }; 208 | } 209 | recursive(context.maxSortedData); 210 | 211 | // Compute Bin Sizes 212 | let binSizes = context.computeBinSizes(binBreaks); 213 | 214 | // Compute Data-> Bin Assignments 215 | let dataBinAssignments = context.computeDataBinAssignments(binBreaks); 216 | 217 | // Return final Bin Object 218 | return { 219 | "rawData": context.rawData, 220 | "data": context.data, 221 | "dataRange": [context.min, context.max], 222 | "binCount": binBreaks.length + 1, 223 | "binBreaks": context.roundToPrecision(binBreaks, context.precision), 224 | "binSizes": binSizes, 225 | "dataBinAssignments": dataBinAssignments 226 | } 227 | 228 | } 229 | 230 | /** 231 | * ckMeans 232 | * Description: The heuristic k-means algorithm, widely used for cluster analysis, does not guarantee optimality. CKMeans is a dynamic programming algorithm for optimal one-dimensional clustering. 233 | * URL: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5148156/ 234 | * @returns { binCount: number, binBreaks: number[], binSizes: object, dataRange: number[], dataBinAssignments: object } 235 | */ 236 | ckMeans() { 237 | let context = this; 238 | let binBreaks: number[] = [] 239 | 240 | let clusters = ss.ckmeans(context.data, context.binCount); 241 | binBreaks = clusters.map(function (cluster: number[]) { 242 | return cluster[cluster.length - 1]; // Last element of each cluster is the bin's upper limit; 243 | }); 244 | binBreaks = binBreaks.slice(0, -1); // Delete the last element. 245 | 246 | // Compute Bin Sizes 247 | let binSizes = context.computeBinSizes(binBreaks); 248 | 249 | // Compute Data-> Bin Assignments 250 | let dataBinAssignments = context.computeDataBinAssignments(binBreaks); 251 | 252 | // Return final Bin Object 253 | return { 254 | "rawData": context.rawData, 255 | "data": context.data, 256 | "dataRange": [context.min, context.max], 257 | "binCount": binBreaks.length + 1, 258 | "binBreaks": context.roundToPrecision(binBreaks, context.precision), 259 | "binSizes": binSizes, 260 | "dataBinAssignments": dataBinAssignments 261 | } 262 | } 263 | 264 | /** 265 | * Equal Interval 266 | * @returns { binCount: number, binBreaks: number[], binSizes: object, dataRange: number[], dataBinAssignments: object } 267 | */ 268 | equalInterval() { 269 | let context = this; 270 | let binBreaks: number[] = []; 271 | let binExtent = (context.max - context.min) / context.binCount; 272 | for (let i = 0; i < (context.binCount - 1); i++) { 273 | let value = context.min + binExtent * (i + 1); 274 | binBreaks.push(value); 275 | } 276 | 277 | // Compute Bin Sizes 278 | let binSizes = context.computeBinSizes(binBreaks); 279 | 280 | // Compute Data-> Bin Assignments 281 | let dataBinAssignments = context.computeDataBinAssignments(binBreaks); 282 | 283 | // Return final Bin Object 284 | return { 285 | "rawData": context.rawData, 286 | "data": context.data, 287 | "dataRange": [context.min, context.max], 288 | "binCount": context.binCount, 289 | "binBreaks": context.roundToPrecision(binBreaks, context.precision), 290 | "binSizes": binSizes, 291 | "dataBinAssignments": dataBinAssignments 292 | } 293 | } 294 | 295 | /** 296 | * Percentile 297 | * @returns { binCount: number, binBreaks: number[], binSizes: object, dataRange: number[], dataBinAssignments: object } 298 | */ 299 | percentile() { 300 | let context = this; 301 | let binBreaks = [ 302 | context.lq1 + Number.EPSILON, // Epsilon is added because the first binBreak must include 1% or lower. 303 | context.lq10, 304 | context.median, 305 | context.uq90, 306 | context.uq99 307 | ]; 308 | 309 | // Compute Bin Sizes 310 | let binSizes = context.computeBinSizes(binBreaks); 311 | 312 | // Compute Data-> Bin Assignments 313 | let dataBinAssignments = context.computeDataBinAssignments(binBreaks); 314 | 315 | // Return final Bin Object 316 | return { 317 | "rawData": context.rawData, 318 | "data": context.data, 319 | "dataRange": [context.min, context.max], 320 | "binCount": binBreaks.length + 1, 321 | "binBreaks": context.roundToPrecision(binBreaks, context.precision), 322 | "binSizes": binSizes, 323 | "dataBinAssignments": dataBinAssignments 324 | } 325 | 326 | } 327 | 328 | /** 329 | * Quantile 330 | * @returns { binCount: number, binBreaks: number[], binSizes: object, dataRange: number[], dataBinAssignments: object } 331 | */ 332 | quantile() { 333 | let context = this; 334 | let binBreaks: number[] = []; 335 | const indexIncrement = Math.floor(context.minSortedData.length / context.binCount); 336 | for (let i = 1; i < context.binCount; i++) { 337 | let value = context.minSortedData[indexIncrement * i]; 338 | binBreaks.push(value); 339 | } 340 | 341 | // Compute Bin Sizes 342 | let binSizes = context.computeBinSizes(binBreaks); 343 | 344 | // Compute Data-> Bin Assignments 345 | let dataBinAssignments = context.computeDataBinAssignments(binBreaks); 346 | 347 | // Return final Bin Object 348 | return { 349 | "rawData": context.rawData, 350 | "data": context.data, 351 | "dataRange": [context.min, context.max], 352 | "binCount": binBreaks.length + 1, 353 | "binBreaks": context.roundToPrecision(binBreaks, context.precision), 354 | "binSizes": binSizes, 355 | "dataBinAssignments": dataBinAssignments 356 | } 357 | 358 | } 359 | 360 | /** 361 | * Mean - Standard Deviation 362 | * @returns { binCount: number, binBreaks: number[], binSizes: object, dataRange: number[], dataBinAssignments: object } 363 | */ 364 | standardDeviation(meanAsBinBoundary=false, sdfactor=1) { 365 | let context = this; 366 | let binBreaks: number[] = []; 367 | let binCount = context.binCount; // We create a copy of binCount because we modify it for a specific case (when it is a odd number) but we don't want to update the global setting. 368 | 369 | if(meanAsBinBoundary){ 370 | let minStart = context.mean - (context.sd * sdfactor * (Math.ceil(binCount / 2) - 1)); 371 | let increment = context.sd * sdfactor; 372 | for (let i = 0; i < (binCount - 1); i++) { 373 | let value = minStart + increment * i; 374 | binBreaks.push(value); 375 | } 376 | }else{ 377 | let minStart = context.mean - (context.sd * sdfactor * (Math.ceil(binCount / 2) - 1)) - context.sd * sdfactor / 2; 378 | let increment = context.sd * sdfactor; 379 | for (let i = 0; i < (binCount - 1); i++) { 380 | let value = minStart + increment * i; 381 | binBreaks.push(value); 382 | } 383 | } 384 | 385 | // Compute Bin Sizes 386 | let binSizes = context.computeBinSizes(binBreaks); 387 | 388 | // Compute Data-> Bin Assignments 389 | let dataBinAssignments = context.computeDataBinAssignments(binBreaks); 390 | 391 | // Return final Bin Object 392 | return { 393 | "rawData": context.rawData, 394 | "data": context.data, 395 | "dataRange": [context.min, context.max], 396 | "binCount": binBreaks.length + 1, 397 | "binBreaks": context.roundToPrecision(binBreaks, context.precision), 398 | "binSizes": binSizes, 399 | "dataBinAssignments": dataBinAssignments 400 | } 401 | 402 | } 403 | 404 | /** 405 | * Manual Interval, similar to User Defined 406 | * @returns { binCount: number, binBreaks: number[], binSizes: object, dataRange: number[], dataBinAssignments: object } 407 | */ 408 | manualInterval(binBreaks = [1, 2, 3]) { 409 | let context = this; 410 | 411 | // Compute Bin Sizes 412 | let binSizes = context.computeBinSizes(binBreaks); 413 | 414 | // Compute Data-> Bin Assignments 415 | let dataBinAssignments = context.computeDataBinAssignments(binBreaks); 416 | 417 | // Return final Bin Object 418 | return { 419 | "rawData": context.rawData, 420 | "data": context.data, 421 | "dataRange": [context.min, context.max], 422 | "binCount": binBreaks.length + 1, 423 | "binBreaks": context.roundToPrecision(binBreaks, context.precision), 424 | "binSizes": binSizes, 425 | "dataBinAssignments": dataBinAssignments 426 | } 427 | 428 | } 429 | 430 | /** 431 | * Pretty Breaks 432 | * @returns { binCount: number, binBreaks: number[], binSizes: object, dataRange: number[], dataBinAssignments: object } 433 | */ 434 | prettyBreaks() { 435 | let context = this; 436 | let binBreaks: number[] = []; 437 | if (context.binCount == 1) { 438 | binBreaks = []; 439 | } else if (context.binCount == 2) { 440 | binBreaks = [parseFloat(context.mean.toPrecision(2))]; 441 | } else { 442 | let binExtent = (context.max - context.min) / context.binCount; 443 | for (let i = 0; i < (context.binCount - 1); i++) { 444 | let value = parseFloat((context.min + binExtent * (i + 1)).toPrecision(2)); 445 | binBreaks.push(value); 446 | } 447 | } 448 | // return [10, 20, 30, 40] 449 | binBreaks = [...new Set(binBreaks)]; // converting it into a set and then into an array because sometimes during prettification, we may end up with same breaks. 450 | 451 | // Compute Bin Sizes 452 | let binSizes = context.computeBinSizes(binBreaks); 453 | 454 | // Compute Data-> Bin Assignments 455 | let dataBinAssignments = context.computeDataBinAssignments(binBreaks); 456 | 457 | // Return final Bin Object 458 | return { 459 | "rawData": context.rawData, 460 | "data": context.data, 461 | "dataRange": [context.min, context.max], 462 | "binCount": binBreaks.length + 1, 463 | "binBreaks": context.roundToPrecision(binBreaks, 0), 464 | "binSizes": binSizes, 465 | "dataBinAssignments": dataBinAssignments 466 | } 467 | 468 | } 469 | 470 | /** 471 | * Box Plot 472 | * @returns { binCount: number, binBreaks: number[], binSizes: object, dataRange: number[], dataBinAssignments: object } 473 | */ 474 | boxPlot() { 475 | let context = this; 476 | const h = 1.5; // `hinge` for determining outliers (the whiskers). You can change the default of 1.5. 477 | // binSize is fixed in this case = 6 478 | let binBreaks = [ 479 | context.lq25 - h * context.iqr, 480 | context.lq25, 481 | context.median, 482 | context.uq75, 483 | context.uq75 + h * context.iqr 484 | ]; 485 | 486 | // Compute Bin Sizes 487 | let binSizes = context.computeBinSizes(binBreaks); 488 | 489 | // Compute Data-> Bin Assignments 490 | let dataBinAssignments = context.computeDataBinAssignments(binBreaks); 491 | 492 | // Return final Bin Object 493 | return { 494 | "rawData": context.rawData, 495 | "data": context.data, 496 | "dataRange": [context.min, context.max], 497 | "binCount": binBreaks.length + 1, 498 | "binBreaks": context.roundToPrecision(binBreaks, context.precision), 499 | "binSizes": binSizes, 500 | "dataBinAssignments": dataBinAssignments 501 | } 502 | 503 | } 504 | 505 | /** 506 | * Defined Interval 507 | * @returns { binCount: number, binBreaks: number[], binSizes: object, dataRange: number[], dataBinAssignments: object } 508 | */ 509 | definedInterval(binExtent:number|any=null) { 510 | let context = this; 511 | 512 | let binBreaks: number[] = []; 513 | let binCount = 1; // binCount is hard-coded here. 514 | 515 | // Use the default `this.binExtent` from the class variable (set in the constructor) unless local variable `binExtent` is supplied. 516 | if(binExtent == null){ 517 | binExtent = this.binExtent; 518 | } 519 | while (context.min + (binExtent * binCount) < context.max) { 520 | binBreaks.push(context.min + binExtent * binCount); 521 | binCount++; 522 | } 523 | 524 | // Compute Bin Sizes 525 | let binSizes = context.computeBinSizes(binBreaks); 526 | 527 | // Compute Data-> Bin Assignments 528 | let dataBinAssignments = context.computeDataBinAssignments(binBreaks); 529 | 530 | // Return final Bin Object 531 | return { 532 | "rawData": context.rawData, 533 | "data": context.data, 534 | "dataRange": [context.min, context.max], 535 | "binCount": binBreaks.length + 1, 536 | "binBreaks": context.roundToPrecision(binBreaks, context.precision), 537 | "binSizes": binSizes, 538 | "dataBinAssignments": dataBinAssignments 539 | } 540 | } 541 | 542 | /** 543 | * Geometric Interval 544 | * Source: `A Python Script for Geometric Interval binning method in QGIS: A Useful Tool for Archaeologists` 545 | * @returns { binCount: number, binBreaks: number[], binSizes: object, dataRange: number[], dataBinAssignments: object } 546 | */ 547 | geometricInterval() { 548 | let context = this; 549 | let binBreaks: number[] = []; 550 | // If min is 0, then the multiplier will turn out to be Infinity; hence we then start from the nonZeroMin as the start. 551 | // An alternative could be Number.EPSILON as it is the smallest value above 0 but it seems to be resulting in weird results. 552 | // ToDo: How does a geometric sequence update when both negative and positive values exist; what is the multiplier in those cases? 553 | let seriesStartVal = context.min == 0 ? context.nonZeroMin : context.min; 554 | let multiplier = Math.pow(context.max / seriesStartVal, 1 / context.binCount); 555 | 556 | // The formula defines bins' upper limits; 557 | // Hence, we run it only until noOfBreaks = (binCount - 1) 558 | for (let i = 0; i < (context.binCount - 1); i++) { 559 | let value = seriesStartVal * Math.pow(multiplier, i + 1); 560 | binBreaks.push(value); 561 | } 562 | 563 | // Compute Bin Sizes 564 | let binSizes = context.computeBinSizes(binBreaks); 565 | 566 | // Compute Data-> Bin Assignments 567 | let dataBinAssignments = context.computeDataBinAssignments(binBreaks); 568 | 569 | // Return final Bin Object 570 | return { 571 | "rawData": context.rawData, 572 | "data": context.data, 573 | "dataRange": [context.min, context.max], 574 | "binCount": binBreaks.length + 1, 575 | "binBreaks": context.roundToPrecision(binBreaks, context.precision), 576 | "binSizes": binSizes, 577 | "dataBinAssignments": dataBinAssignments 578 | } 579 | } 580 | 581 | 582 | /** 583 | * Exponential Bin Size 584 | * Intervals are selected so that the number of observations in each successive interval increases (or decreases) exponentially 585 | * @returns { binCount: number, binBreaks: number[], binSizes: object, dataRange: number[], dataBinAssignments: object } 586 | */ 587 | exponentialBinSizes() { 588 | let context = this; 589 | let binBreaks: number[] = []; 590 | 591 | const firstBinSize = 1; // Heuristic 592 | const seriesSum = context.minSortedData.length; 593 | const equation = firstBinSize.toString() + ' * (1 - x^' + context.binCount.toString() + ') = ' + seriesSum.toString() + ' * (1 - x)'; 594 | const solutions = nerdamer.solveEquations(equation, 'x').map((solution: any) => nerdamer(solution).evaluate().text()); 595 | let commonRatio = 1; 596 | 597 | for (let i = 0; i < solutions.length; i++) { 598 | try { 599 | let numericSolution = parseFloat(solutions[i]); 600 | if (numericSolution != 1) { 601 | commonRatio = numericSolution; 602 | break; 603 | } 604 | } catch (err) { 605 | continue; 606 | } 607 | } 608 | 609 | // If commonRatio is still 1, then there is no geometric, exponential series. 610 | if (commonRatio == 1) { 611 | return []; 612 | } else { 613 | 614 | let cumulativeSizeBins = 0; 615 | for (let i = 0; i < context.binCount - 1; i++) { 616 | 617 | // Size of Nth bin (beginning from firstBinSize and then increasing based on commonRatio) 618 | let nthBinSize = firstBinSize * (Math.pow(commonRatio, i)); 619 | 620 | // Compute Running Sum of number of items covered. 621 | cumulativeSizeBins += nthBinSize; 622 | 623 | // Element Index 624 | const elementIndex = Math.floor(cumulativeSizeBins); 625 | 626 | // Bin Break 627 | const binBreak = context.minSortedData[elementIndex - 1]; // -1 as count and index are off by 1. 628 | 629 | // Push the value for the binBreak to the binBreaks array. 630 | binBreaks.push(binBreak); 631 | } 632 | } 633 | 634 | // Compute Bin Sizes 635 | let binSizes = context.computeBinSizes(binBreaks); 636 | 637 | // Compute Data-> Bin Assignments 638 | let dataBinAssignments = context.computeDataBinAssignments(binBreaks); 639 | 640 | // Return final Bin Object 641 | return { 642 | "rawData": context.rawData, 643 | "data": context.data, 644 | "dataRange": [context.min, context.max], 645 | "binCount": binBreaks.length + 1, 646 | "binBreaks": context.roundToPrecision(binBreaks, context.precision), 647 | "binSizes": binSizes, 648 | "dataBinAssignments": dataBinAssignments 649 | } 650 | } 651 | 652 | /** 653 | * Fisher Jenks 654 | * URL: http://en.wikipedia.org/wiki/Jenks_natural_breaks_optimization 655 | * Implementations: [1](http://danieljlewis.org/files/2010/06/Jenks.pdf) (python), 656 | * [2](https://github.com/vvoovv/djeo-jenks/blob/master/main.js) (buggy), 657 | * [3](https://github.com/simogeo/geostats/blob/master/lib/geostats.js#L407) (works) 658 | * @returns { binCount: number, binBreaks: number[], binSizes: object, dataRange: number[], dataBinAssignments: object } 659 | */ 660 | fisherJenks() { 661 | let context = this; 662 | // Compute the matrices required for Jenks breaks. These matrices 663 | // can be used for any binning of data with `bins <= binCount` 664 | function getMatrices(data: number[], binCount: number) { 665 | 666 | // in the original implementation, these matrices are referred to 667 | // as `LC` and `OP` 668 | // 669 | // * lower_bin_limits (LC): optimal lower bin limits 670 | // * variance_combinations (OP): optimal variance combinations for all bins 671 | var lower_bin_limits: number[][] = [], 672 | variance_combinations: number[][] = [], 673 | // loop counters 674 | i, j, 675 | // the variance, as computed at each step in the calculation 676 | variance = 0; 677 | 678 | // Initialize and fill each matrix with zeroes 679 | for (i = 0; i < data.length + 1; i++) { 680 | var tmp1: number[] = [], tmp2: number[] = []; 681 | for (j = 0; j < binCount + 1; j++) { 682 | tmp1.push(0); 683 | tmp2.push(0); 684 | } 685 | lower_bin_limits.push(tmp1); 686 | variance_combinations.push(tmp2); 687 | } 688 | 689 | for (i = 1; i < binCount + 1; i++) { 690 | lower_bin_limits[1][i] = 1; 691 | variance_combinations[1][i] = 0; 692 | // in the original implementation, 9999999 is used but 693 | // since Javascript has `Infinity`, we use that. 694 | for (j = 2; j < data.length + 1; j++) { 695 | variance_combinations[j][i] = Infinity; 696 | } 697 | } 698 | 699 | for (var l = 2; l < data.length + 1; l++) { 700 | 701 | // `SZ` originally. this is the sum of the values seen thus 702 | // far when calculating variance. 703 | var sum = 0, 704 | // `ZSQ` originally. the sum of squares of values seen 705 | // thus far 706 | sum_squares = 0, 707 | // `WT` originally. This is the number of 708 | w = 0, 709 | // `IV` originally 710 | i4 = 0; 711 | 712 | // in several instances, you could say `Math.pow(x, 2)` 713 | // instead of `x * x`, but this is slower in some browsers 714 | // introduces an unnecessary concept. 715 | for (var m = 1; m < l + 1; m++) { 716 | 717 | // `III` originally 718 | var lower_bin_limit = l - m + 1, 719 | val = data[lower_bin_limit - 1]; 720 | 721 | // here we're estimating variance for each potential binning 722 | // of the data, for each potential number of bins. `w` 723 | // is the number of data points considered so far. 724 | w++; 725 | 726 | // increase the current sum and sum-of-squares 727 | sum += val; 728 | sum_squares += val * val; 729 | 730 | // the variance at this point in the sequence is the difference 731 | // between the sum of squares and the total x 2, over the number 732 | // of samples. 733 | variance = sum_squares - (sum * sum) / w; 734 | 735 | i4 = lower_bin_limit - 1; 736 | 737 | if (i4 !== 0) { 738 | for (j = 2; j < binCount + 1; j++) { 739 | // if adding this element to an existing bin 740 | // will increase its variance beyond the limit, break 741 | // the bin at this point, setting the lower_bin_limit 742 | // at this point. 743 | if (variance_combinations[l][j] >= 744 | (variance + variance_combinations[i4][j - 1])) { 745 | lower_bin_limits[l][j] = lower_bin_limit; 746 | variance_combinations[l][j] = variance + 747 | variance_combinations[i4][j - 1]; 748 | } 749 | } 750 | } 751 | } 752 | 753 | lower_bin_limits[l][1] = 1; 754 | variance_combinations[l][1] = variance; 755 | } 756 | 757 | // return the two matrices. for just providing breaks, only 758 | // `lower_bin_limits` is needed, but variances can be useful to 759 | // evaluage goodness of fit. 760 | return { 761 | lower_bin_limits: lower_bin_limits, 762 | variance_combinations: variance_combinations 763 | }; 764 | } 765 | 766 | // the second part of the jenks recipe: take the calculated matrices 767 | // and derive an array of n breaks. 768 | function breaks(data: number[], lower_bin_limits: number[][], binCount: number) { 769 | 770 | var k = data.length - 1, 771 | kbin: number[] = [], 772 | countNum = binCount; 773 | 774 | // the calculation of bins will never include the upper and 775 | // lower bounds, so we need to explicitly set them 776 | kbin[binCount] = data[data.length - 1]; 777 | kbin[0] = data[0]; 778 | 779 | // the lower_bin_limits matrix is used as indexes into itself 780 | // here: the `k` variable is reused in each iteration. 781 | while (countNum > 1) { 782 | kbin[countNum - 1] = data[lower_bin_limits[k][countNum] - 2]; 783 | k = lower_bin_limits[k][countNum] - 1; 784 | countNum--; 785 | } 786 | 787 | return kbin; 788 | } 789 | 790 | if (context.binCount > context.data.length) return { 791 | "rawData": context.rawData, 792 | "data": context.data, 793 | "dataRange": [context.min, context.max], 794 | "binCount": null, 795 | "binBreaks": [], 796 | "binSizes": { "valids": null, "invalids": null }, 797 | "dataBinAssignments": {} 798 | }; 799 | 800 | // sort data in numerical order, since this is expected 801 | // by the matrices function 802 | context.data = context.data.slice().sort(function (a, b) { return a - b; }); 803 | 804 | // get our basic matrices 805 | var matrices = getMatrices(context.data, context.binCount), 806 | // we only need lower bin limits here 807 | lower_bin_limits = matrices.lower_bin_limits; 808 | 809 | // extract binCount out of the computed matrices 810 | const allBreaks = breaks(context.data, lower_bin_limits, context.binCount); 811 | 812 | let binBreaks = allBreaks.slice(1).slice(0, -1); // this removes the first and last elements of the array because we just need the middle breaks; the `min` and `max` are implicitly inferred any way. 813 | 814 | // Compute Bin Sizes 815 | let binSizes = context.computeBinSizes(binBreaks); 816 | 817 | // Compute Data-> Bin Assignments 818 | let dataBinAssignments = context.computeDataBinAssignments(binBreaks); 819 | 820 | // Return final Bin Object 821 | return { 822 | "rawData": context.rawData, 823 | "data": context.data, 824 | "dataRange": [context.min, context.max], 825 | "binCount": binBreaks.length + 1, 826 | "binBreaks": context.roundToPrecision(binBreaks, context.precision), 827 | "binSizes": binSizes, 828 | "dataBinAssignments": dataBinAssignments 829 | } 830 | 831 | } 832 | 833 | 834 | /** 835 | * Unique 836 | * This method treats each continuous data value as categorical and maps each unique bin of equal values to a distinct color 837 | * @returns { binCount: number, binBreaks: number[], binSizes: object, dataRange: number[], dataBinAssignments: object } 838 | */ 839 | unique() { 840 | let context = this; 841 | const binBreaks: number[] = Array.from(new Set(context.minSortedData)); 842 | 843 | // Compute Bin Sizes 844 | const binSizes = context.computeBinSizes(binBreaks); 845 | 846 | // Compute Data-> Bin Assignments 847 | const dataBinAssignments = context.computeDataBinAssignments(binBreaks); 848 | 849 | // Return final Bin Object 850 | return { 851 | "rawData": context.rawData, 852 | "data": context.data, 853 | "dataRange": [context.min, context.max], 854 | "binCount": binBreaks.length + 1, 855 | "binBreaks": context.roundToPrecision(binBreaks, context.precision), 856 | "binSizes": binSizes, 857 | "dataBinAssignments": dataBinAssignments 858 | } 859 | 860 | } 861 | 862 | 863 | /** 864 | * Unclassed 865 | * @returns { binCount: number, binBreaks: number[], binSizes: object, dataRange: number[], dataBinAssignments: object } 866 | */ 867 | unclassed() { 868 | let context = this; 869 | const binBreaks: number[] = [context.min, context.max]; 870 | 871 | // Compute Bin Sizes 872 | const binSizes = context.computeBinSizes(binBreaks); 873 | 874 | // Compute Data-> Bin Assignments 875 | const dataBinAssignments = context.computeDataBinAssignments(binBreaks); 876 | 877 | // Return final Bin Object 878 | return { 879 | "rawData": context.rawData, 880 | "data": context.data, 881 | "dataRange": [context.min, context.max], 882 | "binCount": null, 883 | "binBreaks": [], 884 | "binSizes": binSizes, 885 | "dataBinAssignments": dataBinAssignments 886 | } 887 | 888 | } 889 | 890 | /** 891 | * Resiliency 892 | * @returns { binCount: number, binBreaks: number[], binSizes: object, dataRange: number[], dataBinAssignments: object } 893 | */ 894 | resiliency(binningMethods: string[] = [], binningMethodObjs:any = {}) { 895 | let context = this; 896 | let binBreaks: number[] = []; 897 | 898 | if(binningMethods.length > 0){ 899 | binningMethods.forEach(function (binningMethod) { 900 | let binObj: any = {}; 901 | switch (binningMethod) { 902 | case EQUAL_INTERVAL: 903 | binObj = context.equalInterval(); 904 | binningMethodObjs[EQUAL_INTERVAL] = JSON.parse(JSON.stringify(binObj)); 905 | break; 906 | 907 | case PERCENTILE: 908 | binObj = context.percentile(); 909 | binningMethodObjs[PERCENTILE] = JSON.parse(JSON.stringify(binObj)); 910 | break; 911 | 912 | case QUANTILE: 913 | binObj = context.quantile(); 914 | binningMethodObjs[QUANTILE] = JSON.parse(JSON.stringify(binObj)); 915 | break; 916 | 917 | case STANDARD_DEVIATION: 918 | binObj = context.standardDeviation(); 919 | binningMethodObjs[STANDARD_DEVIATION] = JSON.parse(JSON.stringify(binObj)); 920 | break; 921 | 922 | case MANUAL_INTERVAL: 923 | binObj = context.manualInterval(); 924 | binningMethodObjs[MANUAL_INTERVAL] = JSON.parse(JSON.stringify(binObj)); 925 | break; 926 | 927 | case PRETTY_BREAKS: 928 | binObj = context.prettyBreaks(); 929 | binningMethodObjs[PRETTY_BREAKS] = JSON.parse(JSON.stringify(binObj)); 930 | break; 931 | 932 | case MAXIMUM_BREAKS: 933 | binObj = context.maximumBreaks(); 934 | binningMethodObjs[MAXIMUM_BREAKS] = JSON.parse(JSON.stringify(binObj)); 935 | break; 936 | 937 | case HEAD_TAIL_BREAKS: 938 | binObj = context.headTailBreaks(); 939 | binningMethodObjs[HEAD_TAIL_BREAKS] = JSON.parse(JSON.stringify(binObj)); 940 | break; 941 | 942 | case CK_MEANS: 943 | binObj = context.ckMeans(); 944 | binningMethodObjs[CK_MEANS] = JSON.parse(JSON.stringify(binObj)); 945 | break; 946 | 947 | case BOXPLOT: 948 | binObj = context.boxPlot(); 949 | binningMethodObjs[BOXPLOT] = JSON.parse(JSON.stringify(binObj)); 950 | break; 951 | 952 | case DEFINED_INTERVAL: 953 | binObj = context.definedInterval(); 954 | binningMethodObjs[DEFINED_INTERVAL] = JSON.parse(JSON.stringify(binObj)); 955 | break; 956 | 957 | case EXPONENTIAL_BIN_SIZE: 958 | binObj = context.exponentialBinSizes(); 959 | binningMethodObjs[EXPONENTIAL_BIN_SIZE] = JSON.parse(JSON.stringify(binObj)); 960 | break; 961 | 962 | case GEOMETRIC_INTERVAL: 963 | binObj = context.geometricInterval(); 964 | binningMethodObjs[GEOMETRIC_INTERVAL] = JSON.parse(JSON.stringify(binObj)); 965 | break; 966 | 967 | case FISHER_JENKS: 968 | binObj = context.fisherJenks(); 969 | binningMethodObjs[FISHER_JENKS] = JSON.parse(JSON.stringify(binObj)); 970 | break; 971 | 972 | default: 973 | ; 974 | } 975 | }); 976 | } 977 | 978 | let frequencyOfMostFrequentBins: any = {}; 979 | let mostFrequentBins: any = {}; 980 | 981 | context.rawData.forEach(function (val, valindex) { 982 | // Let the primary key be index of the item in the rawDataArray. 983 | let primaryKey = valindex.toString(); 984 | if (context.isValid(val)) { 985 | let binAssignmentsForPrimaryKey = Array.from(Object.values(binningMethodObjs)).map((binObj: any) => binObj["dataBinAssignments"][primaryKey]); 986 | if (!(primaryKey in frequencyOfMostFrequentBins)) { 987 | frequencyOfMostFrequentBins[primaryKey] = 0; 988 | } 989 | frequencyOfMostFrequentBins[primaryKey] = context.getFrequencyOfMostFrequentElement(binAssignmentsForPrimaryKey); 990 | if (!(primaryKey in mostFrequentBins)) { 991 | mostFrequentBins[primaryKey] = 0; 992 | } 993 | mostFrequentBins[primaryKey] = context.getMostFrequentElement(binAssignmentsForPrimaryKey); 994 | }else{ 995 | mostFrequentBins[primaryKey] = null; 996 | frequencyOfMostFrequentBins[primaryKey] = null; 997 | } 998 | }); 999 | 1000 | 1001 | // Compute Data for Resiliency 1002 | let resiliencyData: object[] = []; 1003 | Object.keys(frequencyOfMostFrequentBins).forEach(function (primaryKey, valindex) { 1004 | let obj: any = {}; 1005 | if(context.isValid(context.rawData[valindex])){ 1006 | obj["primaryKey"] = primaryKey; 1007 | obj["value"] = context.rawData[valindex]; 1008 | obj["binCandidates"] = []; 1009 | 1010 | Object.keys(binningMethodObjs).forEach(function (binningMethod) { 1011 | obj["binCandidates"].push(JSON.parse(JSON.stringify(binningMethodObjs[binningMethod]["dataBinAssignments"][primaryKey]))); 1012 | }); 1013 | resiliencyData.push(obj); 1014 | } 1015 | }); 1016 | 1017 | let itemwiseBinPriorities: any = {}; 1018 | let itemwiseBinPriorityWeights: any = {}; 1019 | 1020 | resiliencyData.forEach(function (d: any) { 1021 | itemwiseBinPriorities[d["primaryKey"]] = []; 1022 | itemwiseBinPriorityWeights[d["primaryKey"]] = []; 1023 | 1024 | let arr = [...d["binCandidates"]]; 1025 | while (arr.length > 0) { 1026 | const mostFrequentElement = context.getMostFrequentElement(arr); 1027 | const frequencyOfMostFrequentElement = context.getFrequencyOfMostFrequentElement(arr); 1028 | 1029 | // Trim the `arr' to now find the next mostFrequentElement and frequencyOfMostFrequentElement. 1030 | arr = arr.filter(function (item) { return item !== mostFrequentElement }); 1031 | 1032 | // Add to the priority lists 1033 | itemwiseBinPriorities[d["primaryKey"]].push(mostFrequentElement); 1034 | itemwiseBinPriorityWeights[d["primaryKey"]].push(frequencyOfMostFrequentElement); 1035 | } 1036 | }); 1037 | 1038 | // Now, iterate through the TOP priority bins for all data items and put them into those bins. 1039 | 1040 | // Then, compute the min-max of these bins OR basically, determine if they are in an AP. 1041 | // If they are in an arithmetic progression, well and good. 1042 | // If not, there is a need to deprioritize the preferences of the boundary data items and reclassify them to their next best bin priority. 1043 | // Keep doing this until there is a solution. 1044 | 1045 | let binInfo: any = {}; 1046 | let priorityBins: number[] = []; 1047 | resiliencyData.forEach(function (d: any) { 1048 | let priorityBin = itemwiseBinPriorities[d["primaryKey"]][0]; // First element is highest priority. 1049 | if (!(priorityBin in binInfo)) { 1050 | binInfo[priorityBin] = []; 1051 | priorityBins.push(priorityBin); 1052 | } 1053 | binInfo[priorityBin].push(d["value"]); 1054 | }); 1055 | 1056 | // Sort priorityBins from something like [3, 2, 4, 5, 1] to [1, 2, 3, 4, 5] (No harm in doing this) 1057 | priorityBins = priorityBins.sort((n1, n2) => n1 - n2); 1058 | 1059 | // Sort within the priority bins 1060 | priorityBins.forEach(function (priorityBin, valindex) { 1061 | binInfo[priorityBin] = binInfo[priorityBin].sort((n1: number, n2: number) => n1 - n2); 1062 | 1063 | // The first item from the 2nd bin onwards would be the binBreaks. 1064 | // TODO: Consideration: Instead of taking the FIRST element of the 2nd item (or the last element of the 1st item), consider taking the AVERAGE of the two! Might be very interesting as they will absolutely ensure the respective points eventually end up in the appropriate bin, i.e., not get into > or >= dilemmas. 1065 | if (valindex > 0) { 1066 | binBreaks.push(binInfo[priorityBin][0]); 1067 | } 1068 | }); 1069 | 1070 | // New: Round all binBreaks 1071 | binBreaks = binBreaks.map((item) => parseFloat(item.toFixed(2))); 1072 | binBreaks = binBreaks.sort((n1, n2) => n1 - n2); 1073 | 1074 | // Compute Bin Sizes 1075 | let binSizes = context.computeBinSizes(binBreaks); 1076 | 1077 | // Compute Data-> Bin Assignments 1078 | let dataBinAssignments = context.computeDataBinAssignments(binBreaks); 1079 | 1080 | // Return final Bin Object 1081 | return { 1082 | "rawData": context.rawData, 1083 | "data": context.data, 1084 | "dataRange": [context.min, context.max], 1085 | "binCount": binBreaks.length + 1, 1086 | "binBreaks": context.roundToPrecision(binBreaks, context.precision), 1087 | "binSizes": binSizes, 1088 | "dataBinAssignments": dataBinAssignments, 1089 | "binObjs": binningMethodObjs, 1090 | "mostFrequentBins": mostFrequentBins, 1091 | "frequencyOfMostFrequentBins": frequencyOfMostFrequentBins 1092 | }; 1093 | } 1094 | 1095 | // Compute Bin Size from Bin Breaks 1096 | computeBinSizes(binBreaks: number[]) { 1097 | let context = this; 1098 | 1099 | // Reset Bin Sizes; 1100 | let binSizes: any = {}; 1101 | let invalids: number = 0; 1102 | 1103 | // Iterate through all values for the current feature/attribute. 1104 | // Where to put NaNs / nulls? For now, just ignore them; we need valindex hence still need to iterate over all. 1105 | context.rawData.forEach(function (val, valindex) { 1106 | if (context.isValid(val)) { 1107 | 1108 | // We want 1 index, not 0 index. 1109 | let binID = 1; 1110 | if (!(binID in binSizes)) { 1111 | binSizes[binID] = 0; 1112 | } 1113 | for (let i = binID; i < binBreaks.length + 1; i++) { 1114 | if (binBreaks[i - 1] <= val) { 1115 | binID = i + 1; 1116 | if (!(binID in binSizes)) { 1117 | binSizes[binID] = 0; 1118 | } 1119 | } 1120 | } 1121 | 1122 | // Increment the binSizes counter for each binIndex. 1123 | binSizes[binID] += 1; 1124 | 1125 | } 1126 | else { 1127 | invalids++; 1128 | } 1129 | }); 1130 | 1131 | return { "valids": binSizes, "invalids": invalids }; 1132 | } 1133 | 1134 | // Compute Data -> Bin Assignments from Bin Breaks 1135 | computeDataBinAssignments(binBreaks: number[]) { 1136 | let context = this; 1137 | 1138 | let dataBinAssignments: any = {}; 1139 | 1140 | // Iterate through all values for the current feature/attribute. 1141 | // Where to put NaNs / nulls? For now, just ignore them; we need valindex hence still need to iterate over all. 1142 | context.rawData.forEach(function (val, valindex) { 1143 | // Let the primary key be index of the item in the rawDataArray. 1144 | let primaryKey = valindex.toString(); 1145 | 1146 | if (context.isValid(val)) { 1147 | 1148 | // We want 1 index, not 0 index. 1149 | let binID = 1; 1150 | for (let i = binID; i < binBreaks.length + 1; i++) { 1151 | if (binBreaks[i - 1] < val) { 1152 | binID = i + 1; 1153 | } 1154 | } 1155 | // Assign the binId (indexed at 1) to the primaryKey 1156 | dataBinAssignments[primaryKey] = binID; 1157 | } else { 1158 | // For invalid values, the binID will be null, by design choice. 1159 | dataBinAssignments[primaryKey] = null; 1160 | } 1161 | }); 1162 | 1163 | return dataBinAssignments; 1164 | } 1165 | 1166 | /* 1167 | * Return true if the input entity is a valid number and false otherwise. 1168 | */ 1169 | isValid(val: any) { 1170 | return (val != undefined && val != null && val.toString().length > 0 && val.toString().match(/[^0-9\.\-]/g) == null && !Number.isNaN(Number(val))); 1171 | } 1172 | 1173 | /* 1174 | * Round array items 1175 | */ 1176 | roundToPrecision(array: number[], precision = 2) { 1177 | return array.map((item) => parseFloat(item.toFixed(precision))); 1178 | } 1179 | 1180 | 1181 | /* 1182 | * Create a legend-like visualization showing the bin intervals, counts, sizes. Currently using Vega-Lite. 1183 | */ 1184 | visualize(binguruRes: any, binningMethodName: string, colorSchemeCode = "viridis") { 1185 | let context = this; 1186 | /** 1187 | * Important match because `boxPlot` and `standardDeviation` are such that their extents can cross the dataMin and dataMax. 1188 | * Hence, compute [binMin, binMax] 1189 | */ 1190 | let dataMin = context.min; 1191 | let dataMax = context.max; 1192 | let binBreaks = binningMethodName == UNCLASSED ? binguruRes["dataRange"] : binguruRes["binBreaks"]; 1193 | let [binMin, binMax] = [Math.min(...[dataMin, binBreaks[0]]), Math.max(...[dataMax, binBreaks[binBreaks.length - 1]])]; 1194 | 1195 | if(binningMethodName != UNCLASSED){ 1196 | [binMin, binMax] = [parseFloat((binMin * 0.9).toFixed(context.precision)), parseFloat((binMax * 1.1).toFixed(context.precision))]; 1197 | } 1198 | 1199 | let data: object[] = []; 1200 | let dataTicks: number[] = []; 1201 | let binSizes = context.computeBinSizes(binBreaks); 1202 | let validBinSizes = binSizes["valids"]; 1203 | let invalidBinSizes = binSizes["invalids"]; 1204 | 1205 | for (var i = 0; i <= binBreaks.length; i++) { 1206 | let obj: any = {}; 1207 | let binID = (i + 1).toString(); 1208 | if (i == 0) { 1209 | obj["binMin"] = binMin; 1210 | obj["binMax"] = binBreaks[i]; 1211 | 1212 | // Add first binMin 1213 | if (!isNaN(obj["binMin"])) { 1214 | dataTicks.push(obj["binMin"]); 1215 | } 1216 | } 1217 | else if (i <= binBreaks.length - 1) { 1218 | obj["binMin"] = binBreaks[i - 1]; 1219 | obj["binMax"] = binBreaks[i]; 1220 | } 1221 | else { 1222 | obj["binMin"] = binBreaks[i - 1]; 1223 | obj["binMax"] = binMax; 1224 | } 1225 | obj["binningMethod"] = binningMethodName; 1226 | obj["binID"] = binID.toString(); 1227 | obj["binSize"] = validBinSizes[binID]; 1228 | 1229 | // Add all binEnds 1230 | if (!isNaN(obj["binMax"])) { 1231 | dataTicks.push(obj["binMax"]); 1232 | } 1233 | data.push(obj); 1234 | } 1235 | 1236 | const specConstants = { 1237 | width: 700, 1238 | height: 50 1239 | } 1240 | 1241 | let vlSpec = { 1242 | "$schema": "https://vega.github.io/schema/vega-lite/v5.json", 1243 | "width": specConstants.width, 1244 | "height": specConstants.height, 1245 | "background": null, 1246 | "config": { 1247 | "tick": { 1248 | "bandSize": 20 1249 | }, 1250 | "view": { "stroke": null }, 1251 | "axis": { 1252 | "domain": false, "grid": false, "ticks": false 1253 | } 1254 | }, 1255 | "layer": [{ 1256 | "title": null, 1257 | "data": { 1258 | "values": data 1259 | }, 1260 | "mark": { 1261 | "type": "bar", 1262 | "tooltip": { "content": "data" } 1263 | } as any, 1264 | "transform": [{ 1265 | "filter": "datum.binSize != 0" 1266 | }], 1267 | "encoding": { 1268 | "x": { "field": "binMin", "type": "quantitative", "axis": { "title": null, "values": binBreaks, "format": `.${context.precision}f`, "labelFontSize": 16 }, "scale": { "domain": [binMin, binMax] } }, 1269 | "y": { 1270 | "field": "binningMethod", "type": "ordinal", "axis": { 1271 | "title": null, 1272 | // "labelFontSize": 16, 1273 | // "labelLimit": 250, 1274 | // "labelPadding": 10, 1275 | "labels": false 1276 | } 1277 | }, 1278 | "x2": { 1279 | "field": "binMax", "scale": { "domain": [binMin, binMax] }, "axis": { 1280 | "format": `.${context.precision}f`, 1281 | "labelFontSize": 16 1282 | } 1283 | }, 1284 | "size": { 1285 | "field": "binSize", 1286 | "legend": null, 1287 | // "legend": { 1288 | // "titleFontSize": 22, 1289 | // "labelFontSize": 18, 1290 | // "offset": 36 1291 | // }, 1292 | "scale": { 1293 | "type": "linear", 1294 | "range": [5, specConstants.height / 2] 1295 | } 1296 | }, 1297 | "color": { 1298 | "field": "binID", 1299 | "type": "quantitative", 1300 | "scale": { 1301 | "domain": data.map((obj: any) => obj["binID"]), // Important, as otherwise the binIDs are sorted as 1,10,11,..., 2,3,4,5,... 1302 | "scheme": colorSchemeCode, 1303 | "type": "threshold" 1304 | }, 1305 | "legend": null, 1306 | // "legend": { 1307 | // "titleFontSize": 22, 1308 | // "labelFontSize": 18, 1309 | // "offset": 36 1310 | // } 1311 | } 1312 | } 1313 | }, 1314 | { 1315 | "title": null, 1316 | "data": { 1317 | "values": data 1318 | }, 1319 | "mark": { 1320 | "type": "rule", 1321 | "tooltip": { "content": "data" } 1322 | }, 1323 | "transform": [{ 1324 | "filter": "datum.binSize == 0" 1325 | }], 1326 | "encoding": { 1327 | "x": { "field": "binMin", "type": "quantitative", "axis": { "title": null, "values": binBreaks, "format": `.${context.precision}f` }, "scale": { "domain": [binMin, binMax] } }, 1328 | "y": { "field": "binningMethod", "type": "ordinal", "axis": { "title": null, "labelFontSize": 16, "labelLimit": 250, "labelPadding": 10 } }, 1329 | "x2": { "field": "binMax", "scale": { "domain": [binMin, binMax] }, "axis": { "format": `.${context.precision}f` } }, 1330 | "size": { "value": 2 }, 1331 | "strokeDash": { "value": [8, 8] } 1332 | } 1333 | }, 1334 | { 1335 | "title": null, 1336 | "data": { 1337 | "values": dataTicks 1338 | }, 1339 | "mark": { 1340 | "type": "tick", 1341 | "tooltip": { "content": "data" }, 1342 | "fill": "black", 1343 | "orient": "vertical", 1344 | "thickness": 3, 1345 | "height": specConstants.height / 2 1346 | }, 1347 | "encoding": { 1348 | "x": { "field": "data", "type": "quantitative", "scale": { "domain": [binMin, binMax] } } 1349 | } 1350 | } 1351 | ] 1352 | } 1353 | 1354 | // For now, hard-coding the Unclassed binning method to the viridis color scale (as the linear gradient implementation requires discrete 'stop' points and there is no API that returns the list of colors for a Vega-Lite supported color scheme code). 1355 | if (binningMethodName == UNCLASSED) { 1356 | delete vlSpec["layer"][0]["encoding"]["color"]; 1357 | vlSpec["layer"][0]["mark"]["color"] = { 1358 | "x1": 0, 1359 | "y1": 0, 1360 | "x2": 1, 1361 | "y2": 0, 1362 | "gradient": "linear", 1363 | "stops": [ 1364 | { "offset": 0, "color": "#440154" }, 1365 | { "offset": 0.1, "color": "#48186a" }, 1366 | { "offset": 0.2, "color": "#472d7b" }, 1367 | { "offset": 0.3, "color": "#424086" }, 1368 | { "offset": 0.4, "color": "#3b528b" }, 1369 | { "offset": 0.5, "color": "#33638d" }, 1370 | { "offset": 0.6, "color": "#2c728e" }, 1371 | { "offset": 0.7, "color": "#26828e" }, 1372 | { "offset": 0.8, "color": "#21918c" }, 1373 | { "offset": 0.9, "color": "#21a784" }, 1374 | { "offset": 1, "color": "#29b872" } 1375 | ] 1376 | } 1377 | } 1378 | 1379 | return vlSpec; 1380 | } 1381 | 1382 | 1383 | /* 1384 | * Create a choropleth map, given the output object and 1385 | */ 1386 | map(binguruRes: any, inputData: number[], geoData: any[], inputDataFeature: string, geoDataFeature: string, geoDataLookup:string = "id", inputDataKey:string = "id", colorSchemeCode = "viridis") { 1387 | 1388 | let vlSpec = { 1389 | "$schema": "https://vega.github.io/schema/vega-lite/v5.json", 1390 | "width": 500, 1391 | "height": 300, 1392 | "data": { 1393 | "values": geoData, 1394 | "format": { 1395 | "type": "topojson", 1396 | "feature": geoDataFeature 1397 | } 1398 | }, 1399 | "transform": [{ 1400 | "lookup": geoDataLookup, 1401 | "from": { 1402 | "data": { 1403 | "values": inputData 1404 | }, 1405 | "key": inputDataKey, 1406 | "fields": [inputDataFeature] 1407 | } 1408 | }], 1409 | "projection": { 1410 | "type": "albersUsa" 1411 | }, 1412 | "mark": { type: "geoshape", tooltip: { "content": "data" }, "invalid": null }, 1413 | "encoding": { 1414 | "color": { 1415 | "field": inputDataFeature, 1416 | "type": "quantitative", 1417 | "condition": { 1418 | "test": "!isValid(datum['" + inputDataFeature + "'])", 1419 | "value": null 1420 | }, 1421 | "scale": { 1422 | domain: binguruRes["binBreaks"], 1423 | type: "threshold", 1424 | scheme: colorSchemeCode 1425 | } 1426 | } 1427 | } 1428 | } 1429 | 1430 | return vlSpec; 1431 | } 1432 | } 1433 | -------------------------------------------------------------------------------- /src/typings/nerdamer.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'nerdamer/nerdamer.core.js' { 2 | const nerdamer: any; 3 | export default nerdamer; 4 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 5 | "lib": ["es2019"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 6 | "moduleResolution": "node", 7 | "outDir": "./lib/", /* Specify an output folder for all emitted files. */ 8 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 9 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 10 | "strict": true, /* Enable all strict type-checking options. */ 11 | "skipLibCheck": true, /* Skip type checking all .d.ts files. */ 12 | "declaration": true, // generates declaration files 13 | }, 14 | "files": [ 15 | "src/index.ts", 16 | "src/typings/nerdamer.d.ts" 17 | ] 18 | } --------------------------------------------------------------------------------