├── .gitignore ├── .npmignore ├── LICENSE ├── package.json ├── pnpm-lock.yaml ├── src ├── histogram.d.ts └── histogram.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | node_modules 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | lib/tsconfig.tsbuildinfo 2 | pnpm-lock.yaml 3 | src 4 | tsconfig.json 5 | tsconfig.tsbuildinfo 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Christoph Nakazawa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nkzw/histogram", 3 | "version": "1.1.0", 4 | "author": "Christoph Nakazawa ", 5 | "description": "A tiny utility to print horizontal histograms based on an array of numbers.", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/nkzw-tech/histogram.git" 9 | }, 10 | "license": "MIT", 11 | "main": "lib/histogram.js", 12 | "type": "module", 13 | "keywords": [ 14 | "histogram", 15 | "terminal" 16 | ], 17 | "engines": { 18 | "node": ">=18.0.0" 19 | }, 20 | "devDependencies": { 21 | "esbuild": "^0.23.0", 22 | "typescript": "^5.5.3" 23 | }, 24 | "scripts": { 25 | "build": "mkdir -p lib && rm lib/*; tsc --declaration --emitDeclarationOnly --noEmit false && esbuild --target=node18 --format=esm --platform=node --outfile=lib/histogram.js --external:canvas --bundle ./src/histogram.js", 26 | "test": "tsc" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | chalk: 12 | specifier: ^5.3.0 13 | version: 5.3.0 14 | devDependencies: 15 | esbuild: 16 | specifier: ^0.23.0 17 | version: 0.23.0 18 | typescript: 19 | specifier: ^5.5.3 20 | version: 5.5.3 21 | 22 | packages: 23 | 24 | '@esbuild/aix-ppc64@0.23.0': 25 | resolution: {integrity: sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==} 26 | engines: {node: '>=18'} 27 | cpu: [ppc64] 28 | os: [aix] 29 | 30 | '@esbuild/android-arm64@0.23.0': 31 | resolution: {integrity: sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==} 32 | engines: {node: '>=18'} 33 | cpu: [arm64] 34 | os: [android] 35 | 36 | '@esbuild/android-arm@0.23.0': 37 | resolution: {integrity: sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==} 38 | engines: {node: '>=18'} 39 | cpu: [arm] 40 | os: [android] 41 | 42 | '@esbuild/android-x64@0.23.0': 43 | resolution: {integrity: sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==} 44 | engines: {node: '>=18'} 45 | cpu: [x64] 46 | os: [android] 47 | 48 | '@esbuild/darwin-arm64@0.23.0': 49 | resolution: {integrity: sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==} 50 | engines: {node: '>=18'} 51 | cpu: [arm64] 52 | os: [darwin] 53 | 54 | '@esbuild/darwin-x64@0.23.0': 55 | resolution: {integrity: sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==} 56 | engines: {node: '>=18'} 57 | cpu: [x64] 58 | os: [darwin] 59 | 60 | '@esbuild/freebsd-arm64@0.23.0': 61 | resolution: {integrity: sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==} 62 | engines: {node: '>=18'} 63 | cpu: [arm64] 64 | os: [freebsd] 65 | 66 | '@esbuild/freebsd-x64@0.23.0': 67 | resolution: {integrity: sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==} 68 | engines: {node: '>=18'} 69 | cpu: [x64] 70 | os: [freebsd] 71 | 72 | '@esbuild/linux-arm64@0.23.0': 73 | resolution: {integrity: sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==} 74 | engines: {node: '>=18'} 75 | cpu: [arm64] 76 | os: [linux] 77 | 78 | '@esbuild/linux-arm@0.23.0': 79 | resolution: {integrity: sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==} 80 | engines: {node: '>=18'} 81 | cpu: [arm] 82 | os: [linux] 83 | 84 | '@esbuild/linux-ia32@0.23.0': 85 | resolution: {integrity: sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==} 86 | engines: {node: '>=18'} 87 | cpu: [ia32] 88 | os: [linux] 89 | 90 | '@esbuild/linux-loong64@0.23.0': 91 | resolution: {integrity: sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==} 92 | engines: {node: '>=18'} 93 | cpu: [loong64] 94 | os: [linux] 95 | 96 | '@esbuild/linux-mips64el@0.23.0': 97 | resolution: {integrity: sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==} 98 | engines: {node: '>=18'} 99 | cpu: [mips64el] 100 | os: [linux] 101 | 102 | '@esbuild/linux-ppc64@0.23.0': 103 | resolution: {integrity: sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==} 104 | engines: {node: '>=18'} 105 | cpu: [ppc64] 106 | os: [linux] 107 | 108 | '@esbuild/linux-riscv64@0.23.0': 109 | resolution: {integrity: sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==} 110 | engines: {node: '>=18'} 111 | cpu: [riscv64] 112 | os: [linux] 113 | 114 | '@esbuild/linux-s390x@0.23.0': 115 | resolution: {integrity: sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==} 116 | engines: {node: '>=18'} 117 | cpu: [s390x] 118 | os: [linux] 119 | 120 | '@esbuild/linux-x64@0.23.0': 121 | resolution: {integrity: sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==} 122 | engines: {node: '>=18'} 123 | cpu: [x64] 124 | os: [linux] 125 | 126 | '@esbuild/netbsd-x64@0.23.0': 127 | resolution: {integrity: sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==} 128 | engines: {node: '>=18'} 129 | cpu: [x64] 130 | os: [netbsd] 131 | 132 | '@esbuild/openbsd-arm64@0.23.0': 133 | resolution: {integrity: sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==} 134 | engines: {node: '>=18'} 135 | cpu: [arm64] 136 | os: [openbsd] 137 | 138 | '@esbuild/openbsd-x64@0.23.0': 139 | resolution: {integrity: sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==} 140 | engines: {node: '>=18'} 141 | cpu: [x64] 142 | os: [openbsd] 143 | 144 | '@esbuild/sunos-x64@0.23.0': 145 | resolution: {integrity: sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==} 146 | engines: {node: '>=18'} 147 | cpu: [x64] 148 | os: [sunos] 149 | 150 | '@esbuild/win32-arm64@0.23.0': 151 | resolution: {integrity: sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==} 152 | engines: {node: '>=18'} 153 | cpu: [arm64] 154 | os: [win32] 155 | 156 | '@esbuild/win32-ia32@0.23.0': 157 | resolution: {integrity: sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==} 158 | engines: {node: '>=18'} 159 | cpu: [ia32] 160 | os: [win32] 161 | 162 | '@esbuild/win32-x64@0.23.0': 163 | resolution: {integrity: sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==} 164 | engines: {node: '>=18'} 165 | cpu: [x64] 166 | os: [win32] 167 | 168 | chalk@5.3.0: 169 | resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} 170 | engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} 171 | 172 | esbuild@0.23.0: 173 | resolution: {integrity: sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==} 174 | engines: {node: '>=18'} 175 | hasBin: true 176 | 177 | typescript@5.5.3: 178 | resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==} 179 | engines: {node: '>=14.17'} 180 | hasBin: true 181 | 182 | snapshots: 183 | 184 | '@esbuild/aix-ppc64@0.23.0': 185 | optional: true 186 | 187 | '@esbuild/android-arm64@0.23.0': 188 | optional: true 189 | 190 | '@esbuild/android-arm@0.23.0': 191 | optional: true 192 | 193 | '@esbuild/android-x64@0.23.0': 194 | optional: true 195 | 196 | '@esbuild/darwin-arm64@0.23.0': 197 | optional: true 198 | 199 | '@esbuild/darwin-x64@0.23.0': 200 | optional: true 201 | 202 | '@esbuild/freebsd-arm64@0.23.0': 203 | optional: true 204 | 205 | '@esbuild/freebsd-x64@0.23.0': 206 | optional: true 207 | 208 | '@esbuild/linux-arm64@0.23.0': 209 | optional: true 210 | 211 | '@esbuild/linux-arm@0.23.0': 212 | optional: true 213 | 214 | '@esbuild/linux-ia32@0.23.0': 215 | optional: true 216 | 217 | '@esbuild/linux-loong64@0.23.0': 218 | optional: true 219 | 220 | '@esbuild/linux-mips64el@0.23.0': 221 | optional: true 222 | 223 | '@esbuild/linux-ppc64@0.23.0': 224 | optional: true 225 | 226 | '@esbuild/linux-riscv64@0.23.0': 227 | optional: true 228 | 229 | '@esbuild/linux-s390x@0.23.0': 230 | optional: true 231 | 232 | '@esbuild/linux-x64@0.23.0': 233 | optional: true 234 | 235 | '@esbuild/netbsd-x64@0.23.0': 236 | optional: true 237 | 238 | '@esbuild/openbsd-arm64@0.23.0': 239 | optional: true 240 | 241 | '@esbuild/openbsd-x64@0.23.0': 242 | optional: true 243 | 244 | '@esbuild/sunos-x64@0.23.0': 245 | optional: true 246 | 247 | '@esbuild/win32-arm64@0.23.0': 248 | optional: true 249 | 250 | '@esbuild/win32-ia32@0.23.0': 251 | optional: true 252 | 253 | '@esbuild/win32-x64@0.23.0': 254 | optional: true 255 | 256 | chalk@5.3.0: {} 257 | 258 | esbuild@0.23.0: 259 | optionalDependencies: 260 | '@esbuild/aix-ppc64': 0.23.0 261 | '@esbuild/android-arm': 0.23.0 262 | '@esbuild/android-arm64': 0.23.0 263 | '@esbuild/android-x64': 0.23.0 264 | '@esbuild/darwin-arm64': 0.23.0 265 | '@esbuild/darwin-x64': 0.23.0 266 | '@esbuild/freebsd-arm64': 0.23.0 267 | '@esbuild/freebsd-x64': 0.23.0 268 | '@esbuild/linux-arm': 0.23.0 269 | '@esbuild/linux-arm64': 0.23.0 270 | '@esbuild/linux-ia32': 0.23.0 271 | '@esbuild/linux-loong64': 0.23.0 272 | '@esbuild/linux-mips64el': 0.23.0 273 | '@esbuild/linux-ppc64': 0.23.0 274 | '@esbuild/linux-riscv64': 0.23.0 275 | '@esbuild/linux-s390x': 0.23.0 276 | '@esbuild/linux-x64': 0.23.0 277 | '@esbuild/netbsd-x64': 0.23.0 278 | '@esbuild/openbsd-arm64': 0.23.0 279 | '@esbuild/openbsd-x64': 0.23.0 280 | '@esbuild/sunos-x64': 0.23.0 281 | '@esbuild/win32-arm64': 0.23.0 282 | '@esbuild/win32-ia32': 0.23.0 283 | '@esbuild/win32-x64': 0.23.0 284 | 285 | typescript@5.5.3: {} 286 | -------------------------------------------------------------------------------- /src/histogram.d.ts: -------------------------------------------------------------------------------- 1 | export default function histogram(data: ReadonlyArray): string; 2 | -------------------------------------------------------------------------------- /src/histogram.ts: -------------------------------------------------------------------------------- 1 | const bars = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█']; 2 | const barLength = bars.length - 1; 3 | 4 | export default function histogram( 5 | data: ReadonlyArray, 6 | dim = (string: string) => `\x1B[2m${string}\x1B[22m` 7 | ) { 8 | const count = new Map(); 9 | for (const entry of data) { 10 | count.set(entry, (count.get(entry) || 0) + 1); 11 | } 12 | 13 | const keys = [...count.keys()]; 14 | const maxCount = Math.max(...count.values()); 15 | const min = Math.min(...keys); 16 | const max = Math.max(...keys); 17 | const histogram = []; 18 | for (let i = min; i <= max; i++) { 19 | const value = count.get(i); 20 | if (value != null) { 21 | histogram.push( 22 | bars[Math.min(Math.floor((value / maxCount) * barLength), barLength)] 23 | ); 24 | } else { 25 | histogram.push(dim(bars[0])); 26 | } 27 | } 28 | 29 | return `${min} ${histogram.join('')} ${max}`; 30 | } 31 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "allowImportingTsExtensions": true, 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "incremental": true, 8 | "isolatedModules": true, 9 | "jsx": "preserve", 10 | "lib": ["dom", "dom.iterable", "esnext"], 11 | "module": "nodenext", 12 | "moduleResolution": "nodenext", 13 | "noEmit": true, 14 | "outDir": "./lib", 15 | "noImplicitOverride": true, 16 | "noUnusedLocals": true, 17 | "resolveJsonModule": true, 18 | "skipLibCheck": true, 19 | "strict": true 20 | }, 21 | "exclude": ["node_modules"], 22 | "include": ["**/*.ts", "**/*.tsx"] 23 | } 24 | --------------------------------------------------------------------------------