├── .gitignore ├── .npmignore ├── bower.json ├── demo ├── index.html ├── module.html ├── screenshot.png └── server.js ├── package-lock.json ├── package.json ├── readme.md └── source ├── sparkline.js └── sparkline.mjs /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | demo/ 2 | bower.json 3 | .git* -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sparklines", 3 | "authors": [ 4 | "Marius Gundersen " 5 | ], 6 | "description": "Tiny project for drawing sparklines in your webapp", 7 | "main": "source/sparkline.js", 8 | "keywords": [ 9 | "sparkline", 10 | "sparklines" 11 | ], 12 | "license": "MIT", 13 | "homepage": "http://lab.mariusgundersen.net/sparkline", 14 | "repository": "git@github.com:mariusGundersen/sparkline.git", 15 | "ignore": [ 16 | "**/.*", 17 | "node_modules", 18 | "bower_components", 19 | "test", 20 | "tests", 21 | "demo" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Sparkline demo page 4 | 38 | 39 | 40 | 41 |

Sparkline Demo page

42 | 43 | 47 | 48 | 49 |
50 | Here is a sparkline:
51 | Here is another sparkline:
52 | Move your mouse:
53 | This is a sparkline with only one value:
54 | This is a sparkline with no values:
55 | This is a sparkline with all same values:
56 | Look at this one, it has tooltip on hover:
57 | And this one, with a custom height:
58 | This one has a fixed range, and tends to low values:
59 | This one has a fixed range, and tends to high values:
60 | Show the first value: 61 |
62 |
63 |
64 | 65 | 66 | 67 | 75 | 76 | 77 | 78 |
Name 68 | Last week 69 | This week 70 | Today 71 | Minimum 72 | Average 73 | Maximum 74 |
79 | 80 | 81 | 82 | 83 | 324 | 325 | -------------------------------------------------------------------------------- /demo/module.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Sparkline demo page 4 | 38 | 39 | 40 | 41 |

Sparkline Demo page

42 | 43 |
44 | Here is a sparkline:
45 | Here is another sparkline:
46 | Move your mouse:
47 | This is a sparkline with only one value:
48 | This is a sparkline with no values:
49 | This is a sparkline with all same values:
50 | Look at this one, it has tooltip on hover:
51 | And this one, with a custom height:
52 | This one has a fixed range, and tends to low values:
53 | This one has a fixed range, and tends to high values:
54 | Show the first value: 55 |
56 |
57 |
58 | 59 | 60 | 61 | 69 | 70 | 71 | 72 |
Name 62 | Last week 63 | This week 64 | Today 65 | Minimum 66 | Average 67 | Maximum 68 |
73 | 74 | 75 | 76 | 317 | 318 | -------------------------------------------------------------------------------- /demo/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariusGundersen/sparkline/909da5007d1c46a35b17a4911dbfb40dc0f8ca3b/demo/screenshot.png -------------------------------------------------------------------------------- /demo/server.js: -------------------------------------------------------------------------------- 1 | var express = require("express"); 2 | 3 | express() 4 | .use(express.static(__dirname)) 5 | .use(express.static(__dirname + '/../source')) 6 | .listen(3000); 7 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sparklines", 3 | "version": "1.2.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "accepts": { 8 | "version": "1.3.5", 9 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", 10 | "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", 11 | "dev": true, 12 | "requires": { 13 | "mime-types": "~2.1.18", 14 | "negotiator": "0.6.1" 15 | } 16 | }, 17 | "array-flatten": { 18 | "version": "1.1.1", 19 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 20 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", 21 | "dev": true 22 | }, 23 | "body-parser": { 24 | "version": "1.18.2", 25 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", 26 | "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", 27 | "dev": true, 28 | "requires": { 29 | "bytes": "3.0.0", 30 | "content-type": "~1.0.4", 31 | "debug": "2.6.9", 32 | "depd": "~1.1.1", 33 | "http-errors": "~1.6.2", 34 | "iconv-lite": "0.4.19", 35 | "on-finished": "~2.3.0", 36 | "qs": "6.5.1", 37 | "raw-body": "2.3.2", 38 | "type-is": "~1.6.15" 39 | } 40 | }, 41 | "bytes": { 42 | "version": "3.0.0", 43 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", 44 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", 45 | "dev": true 46 | }, 47 | "content-disposition": { 48 | "version": "0.5.2", 49 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 50 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", 51 | "dev": true 52 | }, 53 | "content-type": { 54 | "version": "1.0.4", 55 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 56 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", 57 | "dev": true 58 | }, 59 | "cookie": { 60 | "version": "0.3.1", 61 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 62 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", 63 | "dev": true 64 | }, 65 | "cookie-signature": { 66 | "version": "1.0.6", 67 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 68 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", 69 | "dev": true 70 | }, 71 | "debug": { 72 | "version": "2.6.9", 73 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 74 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 75 | "dev": true, 76 | "requires": { 77 | "ms": "2.0.0" 78 | } 79 | }, 80 | "depd": { 81 | "version": "1.1.2", 82 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 83 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", 84 | "dev": true 85 | }, 86 | "destroy": { 87 | "version": "1.0.4", 88 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 89 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", 90 | "dev": true 91 | }, 92 | "ee-first": { 93 | "version": "1.1.1", 94 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 95 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", 96 | "dev": true 97 | }, 98 | "encodeurl": { 99 | "version": "1.0.2", 100 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 101 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", 102 | "dev": true 103 | }, 104 | "escape-html": { 105 | "version": "1.0.3", 106 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 107 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", 108 | "dev": true 109 | }, 110 | "etag": { 111 | "version": "1.8.1", 112 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 113 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", 114 | "dev": true 115 | }, 116 | "express": { 117 | "version": "4.16.3", 118 | "resolved": "http://registry.npmjs.org/express/-/express-4.16.3.tgz", 119 | "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", 120 | "dev": true, 121 | "requires": { 122 | "accepts": "~1.3.5", 123 | "array-flatten": "1.1.1", 124 | "body-parser": "1.18.2", 125 | "content-disposition": "0.5.2", 126 | "content-type": "~1.0.4", 127 | "cookie": "0.3.1", 128 | "cookie-signature": "1.0.6", 129 | "debug": "2.6.9", 130 | "depd": "~1.1.2", 131 | "encodeurl": "~1.0.2", 132 | "escape-html": "~1.0.3", 133 | "etag": "~1.8.1", 134 | "finalhandler": "1.1.1", 135 | "fresh": "0.5.2", 136 | "merge-descriptors": "1.0.1", 137 | "methods": "~1.1.2", 138 | "on-finished": "~2.3.0", 139 | "parseurl": "~1.3.2", 140 | "path-to-regexp": "0.1.7", 141 | "proxy-addr": "~2.0.3", 142 | "qs": "6.5.1", 143 | "range-parser": "~1.2.0", 144 | "safe-buffer": "5.1.1", 145 | "send": "0.16.2", 146 | "serve-static": "1.13.2", 147 | "setprototypeof": "1.1.0", 148 | "statuses": "~1.4.0", 149 | "type-is": "~1.6.16", 150 | "utils-merge": "1.0.1", 151 | "vary": "~1.1.2" 152 | } 153 | }, 154 | "finalhandler": { 155 | "version": "1.1.1", 156 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", 157 | "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", 158 | "dev": true, 159 | "requires": { 160 | "debug": "2.6.9", 161 | "encodeurl": "~1.0.2", 162 | "escape-html": "~1.0.3", 163 | "on-finished": "~2.3.0", 164 | "parseurl": "~1.3.2", 165 | "statuses": "~1.4.0", 166 | "unpipe": "~1.0.0" 167 | } 168 | }, 169 | "forwarded": { 170 | "version": "0.1.2", 171 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 172 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", 173 | "dev": true 174 | }, 175 | "fresh": { 176 | "version": "0.5.2", 177 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 178 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", 179 | "dev": true 180 | }, 181 | "http-errors": { 182 | "version": "1.6.3", 183 | "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", 184 | "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", 185 | "dev": true, 186 | "requires": { 187 | "depd": "~1.1.2", 188 | "inherits": "2.0.3", 189 | "setprototypeof": "1.1.0", 190 | "statuses": ">= 1.4.0 < 2" 191 | } 192 | }, 193 | "iconv-lite": { 194 | "version": "0.4.19", 195 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", 196 | "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", 197 | "dev": true 198 | }, 199 | "inherits": { 200 | "version": "2.0.3", 201 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 202 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 203 | "dev": true 204 | }, 205 | "ipaddr.js": { 206 | "version": "1.8.0", 207 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", 208 | "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=", 209 | "dev": true 210 | }, 211 | "media-typer": { 212 | "version": "0.3.0", 213 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 214 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", 215 | "dev": true 216 | }, 217 | "merge-descriptors": { 218 | "version": "1.0.1", 219 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 220 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", 221 | "dev": true 222 | }, 223 | "methods": { 224 | "version": "1.1.2", 225 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 226 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", 227 | "dev": true 228 | }, 229 | "mime": { 230 | "version": "1.4.1", 231 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", 232 | "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", 233 | "dev": true 234 | }, 235 | "mime-db": { 236 | "version": "1.36.0", 237 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", 238 | "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==", 239 | "dev": true 240 | }, 241 | "mime-types": { 242 | "version": "2.1.20", 243 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", 244 | "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", 245 | "dev": true, 246 | "requires": { 247 | "mime-db": "~1.36.0" 248 | } 249 | }, 250 | "ms": { 251 | "version": "2.0.0", 252 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 253 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 254 | "dev": true 255 | }, 256 | "negotiator": { 257 | "version": "0.6.1", 258 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 259 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", 260 | "dev": true 261 | }, 262 | "on-finished": { 263 | "version": "2.3.0", 264 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 265 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 266 | "dev": true, 267 | "requires": { 268 | "ee-first": "1.1.1" 269 | } 270 | }, 271 | "parseurl": { 272 | "version": "1.3.2", 273 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", 274 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", 275 | "dev": true 276 | }, 277 | "path-to-regexp": { 278 | "version": "0.1.7", 279 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 280 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", 281 | "dev": true 282 | }, 283 | "proxy-addr": { 284 | "version": "2.0.4", 285 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", 286 | "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", 287 | "dev": true, 288 | "requires": { 289 | "forwarded": "~0.1.2", 290 | "ipaddr.js": "1.8.0" 291 | } 292 | }, 293 | "qs": { 294 | "version": "6.5.1", 295 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", 296 | "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", 297 | "dev": true 298 | }, 299 | "range-parser": { 300 | "version": "1.2.0", 301 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 302 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", 303 | "dev": true 304 | }, 305 | "raw-body": { 306 | "version": "2.3.2", 307 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", 308 | "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", 309 | "dev": true, 310 | "requires": { 311 | "bytes": "3.0.0", 312 | "http-errors": "1.6.2", 313 | "iconv-lite": "0.4.19", 314 | "unpipe": "1.0.0" 315 | }, 316 | "dependencies": { 317 | "depd": { 318 | "version": "1.1.1", 319 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", 320 | "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", 321 | "dev": true 322 | }, 323 | "http-errors": { 324 | "version": "1.6.2", 325 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", 326 | "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", 327 | "dev": true, 328 | "requires": { 329 | "depd": "1.1.1", 330 | "inherits": "2.0.3", 331 | "setprototypeof": "1.0.3", 332 | "statuses": ">= 1.3.1 < 2" 333 | } 334 | }, 335 | "setprototypeof": { 336 | "version": "1.0.3", 337 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 338 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", 339 | "dev": true 340 | } 341 | } 342 | }, 343 | "safe-buffer": { 344 | "version": "5.1.1", 345 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 346 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", 347 | "dev": true 348 | }, 349 | "send": { 350 | "version": "0.16.2", 351 | "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", 352 | "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", 353 | "dev": true, 354 | "requires": { 355 | "debug": "2.6.9", 356 | "depd": "~1.1.2", 357 | "destroy": "~1.0.4", 358 | "encodeurl": "~1.0.2", 359 | "escape-html": "~1.0.3", 360 | "etag": "~1.8.1", 361 | "fresh": "0.5.2", 362 | "http-errors": "~1.6.2", 363 | "mime": "1.4.1", 364 | "ms": "2.0.0", 365 | "on-finished": "~2.3.0", 366 | "range-parser": "~1.2.0", 367 | "statuses": "~1.4.0" 368 | } 369 | }, 370 | "serve-static": { 371 | "version": "1.13.2", 372 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", 373 | "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", 374 | "dev": true, 375 | "requires": { 376 | "encodeurl": "~1.0.2", 377 | "escape-html": "~1.0.3", 378 | "parseurl": "~1.3.2", 379 | "send": "0.16.2" 380 | } 381 | }, 382 | "setprototypeof": { 383 | "version": "1.1.0", 384 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", 385 | "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", 386 | "dev": true 387 | }, 388 | "statuses": { 389 | "version": "1.4.0", 390 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 391 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", 392 | "dev": true 393 | }, 394 | "type-is": { 395 | "version": "1.6.16", 396 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", 397 | "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", 398 | "dev": true, 399 | "requires": { 400 | "media-typer": "0.3.0", 401 | "mime-types": "~2.1.18" 402 | } 403 | }, 404 | "unpipe": { 405 | "version": "1.0.0", 406 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 407 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", 408 | "dev": true 409 | }, 410 | "utils-merge": { 411 | "version": "1.0.1", 412 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 413 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", 414 | "dev": true 415 | }, 416 | "vary": { 417 | "version": "1.1.2", 418 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 419 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", 420 | "dev": true 421 | } 422 | } 423 | } 424 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sparklines", 3 | "version": "1.3.0", 4 | "description": "Tiny project for drawing sparklines in your webapp", 5 | "main": "source/sparkline.js", 6 | "module": "source/sparkline.mjs", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "start": "node demo/server.js" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "http://github.com/mariusGundersen/sparkline.git" 14 | }, 15 | "keywords": [ 16 | "sparkline", 17 | "sparklines" 18 | ], 19 | "author": "", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/mariusGundersen/sparkline/issues" 23 | }, 24 | "devDependencies": { 25 | "express": "^4.9.5" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Sparkline 2 | 3 | [Sparklines](http://en.wikipedia.org/wiki/Sparkline) are tiny line graphs draws inline in the document. They are designed to present the overal trend in data without giving detailed information about a dataset. 4 | 5 | ![Screenshot of sparklines demo page](https://raw.github.com/mariusGundersen/sparkline/master/demo/screenshot.png) 6 | 7 | ## Installation 8 | 9 | Sparkline can be installed from bower and npm: 10 | 11 | ``` 12 | npm install sparklines 13 | ``` 14 | 15 | or 16 | 17 | ``` 18 | bower install sparklines 19 | ``` 20 | 21 | ## Usage 22 | 23 | Sparkline can be used as an es6 module, commonJS module, AMD module or as a normal script file. It makes the `Sparkline` constructor available, which takes one or two parameters, an inline element and an options object. The contents of the element is replaced with the sparkline. 24 | 25 | ```js 26 | var sparkline1 = new Sparkline(document.getElementById("an-inline-element")); 27 | var sparkline2 = new Sparkline($("span")[0], { width: 200 }); 28 | ``` 29 | 30 | The returned object has a single method, `draw`, which should be called with an array of `Number`s. This method can be called multiple times to redraw the sparkline. 31 | 32 | ```js 33 | sparkline1.draw([1, 2, 3, 4, 5]); 34 | sparkline2.draw([0.5, 0.5, 6, 0.5]); 35 | ``` 36 | 37 | [More examples](http://lab.mariusgundersen.net/sparklines/) 38 | 39 | ## Reference 40 | 41 | ### Constructor 42 | 43 | This is the normal way to create a sparkline instance 44 | 45 | ### `new Sparkline(HTMLElement, options)` 46 | 47 | Returns a sparkline instance. The options object is optional, and overrides the default options if specified. See the section on options further down. 48 | 49 | - `HTMLElement`: An HTMLElement, prefereably an inline element, for example a ``. 50 | - `options` (optional): An object with custom options for this sparkline instance. 51 | 52 | ### Static methods 53 | 54 | These are helper methods for creating sparkline instances 55 | 56 | #### `Sparkline.init(HTMLElement, options)` 57 | 58 | Returns a sparkline instance. This work exactly the same as calling the constructor. The options object is optional, and overrides the default options if specified. See the section on options further down. 59 | 60 | - `HTMLElement`: An HTMLElement, prefereably an inline element, for example a ``. 61 | - `options` (optional): An object with custom options for this sparkline instance. 62 | 63 | #### `Sparkline.draw(HTMLElement, values, options)` 64 | 65 | Draws the values as a sparkline on the given element, with the specified options, before returning the sparkline instance. This is the same as crating a sparkline instance and then calling `draw(values)`. 66 | 67 | - `HTMLElement`: An HTMLElement, prefereably an inline element, for example a ``. 68 | - `values`: An array of numbers to be drawn as a sparkline. 69 | - `options` (optional): An object with custom options for this sparkline instance. 70 | 71 | #### `Sparkline.options` 72 | 73 | An object containing the default options for drawing a sparkline. This is shared by all sparkline instances. Change the values of this object before creating sparkline instances. The options (with default values in parenthesis) available are: 74 | 75 | - `width` (`100`): A number giving the width of the sparkline box in pixels. 76 | - `height` (`null`): A number giving the height of the sparkline box in pixels. By default, uses the height of the Canvas element. 77 | - `lineColor` (`"black"`): A string giving the color of the sparkline. Any valid CSS color, including RGB, HEX and HSV. 78 | - `lineWidth` (`1`): A number giving the stroke of the line in pixels. 79 | - `startColor` (`"transparent"`): A string giving the color of the dot marking the first value. Any valid CSS color. 80 | - `endColor` (`"red"`): A string giving the color of the dot marking the last value. Any valid CSS color. 81 | - `maxColor` (`"transparent"`): A string giving the color of the dot marking the highest value. Any valid CSS color. 82 | - `minColor` (`"transparent"`): A string giving the color of the dot marking the lowest value. Any valid CSS color. 83 | - `minValue` (`null`): A number giving the minimum y-axis value. By default, the lowest data value is used. 84 | - `maxValue` (`null`): A number giving the maximum y-axis value. By default, the highest data value is used. 85 | - `dotRadius` (`2.5`): A number giving the size of the dots used to mark important values. 86 | - `tooltip` (`null`): A function that takes three arguments (`value`, `index`, `array`) and returns a tooltip string to show when the user hovers over the sparkline. By default there is no tooltip. 87 | 88 | ### Instance methods 89 | 90 | #### `sparkline.draw(values)` 91 | 92 | Draws the values as a sparkline in the element given in the constructor. This method can be called multiple times, and will replace the previous sparkline with a new one. 93 | 94 | - `values`: An array of numbers to be drawn as a sparkline 95 | 96 | ## License 97 | 98 | Copyright (C) 2013 Marius Gundersen 99 | 100 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 101 | 102 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 103 | 104 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 105 | -------------------------------------------------------------------------------- /source/sparkline.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | // AMD. Register as an anonymous module. 4 | define(factory); 5 | } else if (typeof exports === 'object') { 6 | // Node. Does not work with strict CommonJS, but 7 | // only CommonJS-like enviroments that support module.exports, 8 | // like Node. 9 | module.exports = factory(); 10 | } else { 11 | // Browser globals (root is window) 12 | root.Sparkline = factory(); 13 | } 14 | }(window, function () { 15 | function extend(specific, general) { 16 | var obj = {}; 17 | for (var key in general) { 18 | obj[key] = key in specific ? specific[key] : general[key]; 19 | } 20 | return obj; 21 | } 22 | 23 | function Sparkline(element, options) { 24 | this.element = element; 25 | this.options = extend(options || {}, Sparkline.options); 26 | 27 | init: { 28 | this.element.innerHTML = ""; 29 | this.canvas = this.element.firstChild; 30 | this.context = this.canvas.getContext("2d"); 31 | this.ratio = window.devicePixelRatio || 1; 32 | 33 | if (this.options.tooltip) { 34 | this.canvas.style.position = "relative"; 35 | this.canvas.onmousemove = showTooltip.bind(this); 36 | } 37 | } 38 | } 39 | 40 | Sparkline.options = { 41 | width: 100, 42 | height: null, 43 | lineColor: "black", 44 | lineWidth: 1.5, 45 | startColor: "transparent", 46 | endColor: "black", 47 | maxColor: "transparent", 48 | minColor: "transparent", 49 | minValue: null, 50 | maxValue: null, 51 | minMaxValue: null, 52 | maxMinValue: null, 53 | dotRadius: 2.5, 54 | tooltip: null, 55 | fillBelow: true, 56 | fillLighten: 0.5, 57 | startLine: false, 58 | endLine: false, 59 | minLine: false, 60 | maxLine: false, 61 | bottomLine: false, 62 | topLine: false, 63 | averageLine: false 64 | }; 65 | 66 | Sparkline.init = function (element, options) { 67 | return new Sparkline(element, options); 68 | }; 69 | 70 | Sparkline.draw = function (element, points, options) { 71 | var sparkline = new Sparkline(element, options); 72 | sparkline.draw(points); 73 | return sparkline; 74 | } 75 | 76 | function getY(minValue, maxValue, offsetY, height, index) { 77 | var range = maxValue - minValue; 78 | if (range == 0) { 79 | return offsetY + height / 2; 80 | } else { 81 | return (offsetY + height) - ((this[index] - minValue) / range) * height; 82 | } 83 | } 84 | 85 | function drawDot(radius, x1, x2, color, line, x, y) { 86 | this.context.beginPath(); 87 | this.context.fillStyle = color; 88 | this.context.arc(x, y, radius, 0, Math.PI * 2, false); 89 | this.context.fill(); 90 | drawLine.call(this, x1, x2, line, x, y); 91 | } 92 | 93 | function drawLine(x1, x2, style, x, y){ 94 | if(!style) return; 95 | 96 | this.context.save(); 97 | this.context.strokeStyle = style.color || 'black'; 98 | this.context.lineWidth = (style.width || 1) * this.ratio; 99 | this.context.globalAlpha = style.alpha || 1; 100 | this.context.beginPath(); 101 | this.context.moveTo(style.direction != 'right' ? x1 : x, y); 102 | this.context.lineTo(style.direction != 'left' ? x2 : x, y); 103 | this.context.stroke(); 104 | this.context.restore(); 105 | } 106 | 107 | function showTooltip(e) { 108 | var x = e.offsetX || e.layerX || 0; 109 | var delta = ((this.options.width - this.options.dotRadius * 2) / (this.points.length - 1)); 110 | var index = minmax(0, Math.round((x - this.options.dotRadius) / delta), this.points.length - 1); 111 | 112 | this.canvas.title = this.options.tooltip(this.points[index], index, this.points); 113 | } 114 | 115 | Sparkline.prototype.draw = function (points) { 116 | 117 | points = points || []; 118 | this.points = points; 119 | 120 | this.canvas.width = this.options.width * this.ratio; 121 | this.canvas.style.width = this.options.width + 'px'; 122 | 123 | var pxHeight = this.options.height || this.element.offsetHeight; 124 | this.canvas.height = pxHeight * this.ratio; 125 | this.canvas.style.height = pxHeight + 'px'; 126 | 127 | var lineWidth = this.options.lineWidth * this.ratio; 128 | var offsetX = Math.max(this.options.dotRadius * this.ratio, lineWidth/2); 129 | var offsetY = Math.max(this.options.dotRadius * this.ratio, lineWidth/2); 130 | var width = this.canvas.width - offsetX * 2; 131 | var height = this.canvas.height - offsetY * 2; 132 | 133 | var minValue = Math.min.apply(Math, points); 134 | var maxValue = Math.max.apply(Math, points); 135 | var bottomValue = this.options.minValue != undefined ? this.options.minValue : Math.min(minValue, this.options.maxMinValue != undefined ? this.options.maxMinValue : minValue); 136 | var topValue = this.options.maxValue != undefined ? this.options.maxValue : Math.max(maxValue, this.options.minMaxValue != undefined ? this.options.minMaxValue : maxValue); 137 | var minX = offsetX; 138 | var maxX = offsetX; 139 | 140 | var x = offsetX; 141 | var y = getY.bind(points, bottomValue, topValue, offsetY, height); 142 | var delta = width / (points.length - 1); 143 | 144 | var dot = drawDot.bind(this, this.options.dotRadius * this.ratio, offsetX, width + offsetX); 145 | var line = drawLine.bind(this, offsetX, width + offsetX); 146 | 147 | this.context.save(); 148 | 149 | this.context.strokeStyle = this.options.lineColor; 150 | this.context.fillStyle = this.options.lineColor; 151 | this.context.lineWidth = lineWidth; 152 | this.context.lineCap = 'round'; 153 | this.context.lineJoin = 'round'; 154 | 155 | if(this.options.fillBelow && points.length > 1){ 156 | this.context.save(); 157 | this.context.beginPath(); 158 | this.context.moveTo(x, y(0)); 159 | for (var i = 1; i < points.length; i++) { 160 | x += delta; 161 | 162 | minX = points[i] == minValue ? x : minX; 163 | maxX = points[i] == maxValue ? x : maxX; 164 | 165 | this.context.lineTo(x, y(i)); 166 | } 167 | this.context.lineTo(width+offsetX, height + offsetY + lineWidth/2); 168 | this.context.lineTo(offsetX, height + offsetY + lineWidth/2); 169 | this.context.fill(); 170 | if(this.options.fillLighten > 0){ 171 | this.context.fillStyle = 'white'; 172 | this.context.globalAlpha = this.options.fillLighten; 173 | this.context.fill(); 174 | this.context.globalAlpha = 1; 175 | }else if(this.options.fillLighten < 0){ 176 | this.context.fillStyle = 'black'; 177 | this.context.globalAlpha = -this.options.fillLighten; 178 | this.context.fill(); 179 | } 180 | this.context.restore(); 181 | } 182 | 183 | x = offsetX; 184 | this.context.beginPath(); 185 | this.context.moveTo(x, y(0)); 186 | for (var i = 1; i < points.length; i++) { 187 | x += delta; 188 | this.context.lineTo(x, y(i)); 189 | } 190 | this.context.stroke(); 191 | 192 | this.context.restore(); 193 | 194 | line(this.options.bottomLine, 0, offsetY); 195 | line(this.options.topLine, 0, height + offsetY+lineWidth/2); 196 | 197 | dot(this.options.startColor, this.options.startLine, offsetX + (points.length == 1 ? width / 2 : 0), y(0)); 198 | dot(this.options.endColor, this.options.endLine, offsetX + (points.length == 1 ? width / 2 : width), y(points.length-1)); 199 | dot(this.options.minColor, this.options.minLine, minX + (points.length == 1 ? width / 2 : 0), y(points.indexOf(minValue))); 200 | dot(this.options.maxColor, this.options.maxLine, maxX + (points.length == 1 ? width / 2 : 0), y(points.indexOf(maxValue))); 201 | 202 | //line(this.options.averageLine, ) 203 | } 204 | 205 | function minmax(a, b, c) { 206 | return Math.max(a, Math.min(b, c)); 207 | } 208 | 209 | return Sparkline; 210 | })); 211 | -------------------------------------------------------------------------------- /source/sparkline.mjs: -------------------------------------------------------------------------------- 1 | export default class Sparkline { 2 | constructor(element, options = {}) { 3 | this.element = element; 4 | this.options = { ...Sparkline.options, ...options }; 5 | 6 | init: { 7 | this.element.innerHTML = ""; 8 | this.canvas = this.element.firstChild; 9 | this.context = this.canvas.getContext("2d"); 10 | this.ratio = window.devicePixelRatio || 1; 11 | 12 | if (this.options.tooltip) { 13 | this.canvas.style.position = "relative"; 14 | this.canvas.addEventListener('mousemove', e => { 15 | const x = e.offsetX || e.layerX || 0; 16 | const delta = ((this.options.width - this.options.dotRadius * 2) / (this._points.length - 1)); 17 | const index = minmax(0, Math.round((x - this.options.dotRadius) / delta), this._points.length - 1); 18 | 19 | this.canvas.title = this.options.tooltip(this._points[index], index, this._points); 20 | }, false); 21 | } 22 | } 23 | } 24 | 25 | set points(points) { 26 | this.draw(points); 27 | } 28 | 29 | get points() { 30 | return this._points; 31 | } 32 | 33 | draw(points = []) { 34 | this._points = points; 35 | 36 | this.canvas.width = this.options.width * this.ratio; 37 | this.canvas.style.width = `${this.options.width}px`; 38 | 39 | const pxHeight = this.options.height || this.element.offsetHeight; 40 | this.canvas.height = pxHeight * this.ratio; 41 | this.canvas.style.height = `${pxHeight}px`; 42 | 43 | const lineWidth = this.options.lineWidth * this.ratio; 44 | const offsetX = Math.max(this.options.dotRadius * this.ratio, lineWidth / 2); 45 | const offsetY = Math.max(this.options.dotRadius * this.ratio, lineWidth / 2); 46 | const width = this.canvas.width - offsetX * 2; 47 | const height = this.canvas.height - offsetY * 2; 48 | 49 | const minValue = Math.min.apply(Math, points); 50 | const maxValue = Math.max.apply(Math, points); 51 | const bottomValue = this.options.minValue != undefined ? this.options.minValue : Math.min(minValue, this.options.maxMinValue != undefined ? this.options.maxMinValue : minValue); 52 | const topValue = this.options.maxValue != undefined ? this.options.maxValue : Math.max(maxValue, this.options.minMaxValue != undefined ? this.options.minMaxValue : maxValue); 53 | let minX = offsetX; 54 | let maxX = offsetX; 55 | 56 | let x = offsetX; 57 | const y = index => (topValue === bottomValue) 58 | ? offsetY + height / 2 59 | : (offsetY + height) - ((points[index] - bottomValue) / (topValue - bottomValue)) * height; 60 | const delta = width / (points.length - 1); 61 | 62 | const line = (style, x, y) => { 63 | if (!style) return; 64 | 65 | this.context.save(); 66 | this.context.strokeStyle = style.color || 'black'; 67 | this.context.lineWidth = (style.width || 1) * this.ratio; 68 | this.context.globalAlpha = style.alpha || 1; 69 | this.context.beginPath(); 70 | this.context.moveTo(style.direction != 'right' ? offsetX : x, y); 71 | this.context.lineTo(style.direction != 'left' ? width + offsetX : x, y); 72 | this.context.stroke(); 73 | this.context.restore(); 74 | } 75 | 76 | const dot = (color, lineStyle, x, y) => { 77 | this.context.beginPath(); 78 | this.context.fillStyle = color; 79 | this.context.arc(x, y, this.options.dotRadius * this.ratio, 0, Math.PI * 2, false); 80 | this.context.fill(); 81 | line(lineStyle, x, y); 82 | } 83 | 84 | this.context.save(); 85 | 86 | this.context.strokeStyle = this.options.lineColor; 87 | this.context.fillStyle = this.options.lineColor; 88 | this.context.lineWidth = lineWidth; 89 | this.context.lineCap = 'round'; 90 | this.context.lineJoin = 'round'; 91 | 92 | if (this.options.fillBelow && points.length > 1) { 93 | this.context.save(); 94 | this.context.beginPath(); 95 | this.context.moveTo(x, y(0)); 96 | for (let i = 1; i < points.length; i++) { 97 | x += delta; 98 | 99 | minX = points[i] == minValue ? x : minX; 100 | maxX = points[i] == maxValue ? x : maxX; 101 | 102 | this.context.lineTo(x, y(i)); 103 | } 104 | this.context.lineTo(width + offsetX, height + offsetY + lineWidth / 2); 105 | this.context.lineTo(offsetX, height + offsetY + lineWidth / 2); 106 | this.context.fill(); 107 | if (this.options.fillLighten > 0) { 108 | this.context.fillStyle = 'white'; 109 | this.context.globalAlpha = this.options.fillLighten; 110 | this.context.fill(); 111 | this.context.globalAlpha = 1; 112 | } else if (this.options.fillLighten < 0) { 113 | this.context.fillStyle = 'black'; 114 | this.context.globalAlpha = -this.options.fillLighten; 115 | this.context.fill(); 116 | } 117 | this.context.restore(); 118 | } 119 | 120 | x = offsetX; 121 | this.context.beginPath(); 122 | this.context.moveTo(x, y(0)); 123 | for (let i = 1; i < points.length; i++) { 124 | x += delta; 125 | this.context.lineTo(x, y(i)); 126 | } 127 | this.context.stroke(); 128 | 129 | this.context.restore(); 130 | 131 | line(this.options.bottomLine, 0, offsetY); 132 | line(this.options.topLine, 0, height + offsetY + lineWidth / 2); 133 | 134 | dot(this.options.startColor, this.options.startLine, offsetX + (points.length == 1 ? width / 2 : 0), y(0)); 135 | dot(this.options.endColor, this.options.endLine, offsetX + (points.length == 1 ? width / 2 : width), y(points.length - 1)); 136 | dot(this.options.minColor, this.options.minLine, minX + (points.length == 1 ? width / 2 : 0), y(points.indexOf(minValue))); 137 | dot(this.options.maxColor, this.options.maxLine, maxX + (points.length == 1 ? width / 2 : 0), y(points.indexOf(maxValue))); 138 | } 139 | 140 | static init(element, options) { 141 | return new Sparkline(element, options); 142 | } 143 | 144 | static draw(element, points, options) { 145 | const sparkline = new Sparkline(element, options); 146 | sparkline.draw(points); 147 | return sparkline; 148 | } 149 | } 150 | 151 | Sparkline.options = { 152 | width: 100, 153 | height: null, 154 | lineColor: "black", 155 | lineWidth: 1.5, 156 | startColor: "transparent", 157 | endColor: "black", 158 | maxColor: "transparent", 159 | minColor: "transparent", 160 | minValue: null, 161 | maxValue: null, 162 | minMaxValue: null, 163 | maxMinValue: null, 164 | dotRadius: 2.5, 165 | tooltip: null, 166 | fillBelow: true, 167 | fillLighten: 0.5, 168 | startLine: false, 169 | endLine: false, 170 | minLine: false, 171 | maxLine: false, 172 | bottomLine: false, 173 | topLine: false, 174 | averageLine: false 175 | }; 176 | 177 | function minmax(a, b, c) { 178 | return Math.max(a, Math.min(b, c)); 179 | } 180 | --------------------------------------------------------------------------------