├── .gitignore ├── .travis.yml ├── README.md ├── dist └── main.css ├── lib ├── axis-title.js ├── chart.js ├── chartist.js ├── index.js ├── legend.js └── main.less ├── license ├── package.json └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | !node_modules/canvas.js 3 | 4 | bundle.js 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | - "6" 5 | notifications: 6 | email: 7 | on_success: change 8 | on_failure: always 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-chartist 2 | 3 | > SVG Charts on the server. 4 | 5 | [](https://travis-ci.org/panosoft/node-chartist) 6 | 7 | Node Chartist is a functional server-side wrapper for the popular [Chartist](http://gionkunz.github.io/chartist-js/index.html) library. It generates static svg charts and adds support for a few useful features such as axis titles and chart legends. 8 | 9 | # Installation 10 | 11 | ```sh 12 | npm install node-chartist 13 | ``` 14 | 15 | The accompanying CSS can be found at `dist/main.css` after installation. 16 | 17 | # Usage 18 | 19 | ```js 20 | const co = require('co'); 21 | const generate = require('node-chartist'); 22 | 23 | co(function * () { 24 | 25 | // options object 26 | const options = {width: 400, height: 200}; 27 | const data = { 28 | labels: ['a','b','c','d','e'], 29 | series: [ 30 | [1, 2, 3, 4, 5], 31 | [3, 4, 5, 6, 7] 32 | ] 33 | }; 34 | const bar = yield generate('bar', options, data); //=> chart HTML 35 | 36 | 37 | // options function 38 | const options = (Chartist) => ({width: 400, height: 200, axisY: { type: Chartist.FixedScaleAxis } }); 39 | const data = { 40 | labels: ['a','b','c','d','e'], 41 | series: [ 42 | [1, 2, 3, 4, 5], 43 | [3, 4, 5, 6, 7] 44 | ] 45 | }; 46 | const bar = yield generate('bar', options, data); //=> chart HTML 47 | 48 | }); 49 | ``` 50 | 51 | # API 52 | 53 | ## generate ( type , options , data ) 54 | 55 | A curried function that generates a static svg chart. 56 | 57 | Returns a `Promise` that is fulfilled with the static chart HTML. 58 | 59 | __Arguments__ 60 | 61 | - `type` - A string used to determine what type of chart to generate. Supported values are: 62 | 63 | - `bar` 64 | - `line` 65 | - `pie` 66 | 67 | 68 | - `options` - An object or a function that returns an object of chart options. If a function is used, it will be called with the `Chartist` object. 69 | 70 | Options are dependent on the chart `type`. All options in the [Chartist Api Documentation](http://gionkunz.github.io/chartist-js/api-documentation.html) are supported. In addition to those, the following options are supported by node-chartist: 71 | 72 | - `axisX.title` - A string to use as the x axis title. 73 | 74 | - `axisY.title` - A string to use as the y axis title. 75 | 76 | - `legend` - A boolean used to determine whether a legend should be generated. Defaults to `true`. 77 | 78 | 79 | - `data` - An object containing data used to generate the chart. The structure of this object depends on chart `type`. Please refer to the [Chartist Api Documentation](http://gionkunz.github.io/chartist-js/api-documentation.html) for complete details. 80 | 81 | For bar and line charts, this object contains the following properties 82 | 83 | - `labels` - An array of string labels to apply to each value. 84 | 85 | - `series` - An array of arrays or objects containing the values to plot. If objects are used, the following properties are supported: 86 | 87 | - `name` - A string specifying the name of the series. The name will be used in the legend and will be set as the ct:series-name attribute on the series group. 88 | 89 | - `value` - An array of values for the series. 90 | 91 | - `className` - A string to override the CSS class name for the series group. 92 | 93 | - `meta` - Meta data is serialized and written to a ct:meta attribute on the series group. 94 | 95 | Examples: 96 | 97 | ```js 98 | const data = { 99 | labels: ['a', 'b', 'c', 'd', 'e'], 100 | series: [ 101 | [1, 2, 3, 4, 5], 102 | [3, 4, 5, 6, 7] 103 | ] 104 | }; 105 | ``` 106 | 107 | ```js 108 | const data = { 109 | labels: ['a', 'b', 'c', 'd', 'e'], 110 | series: [ 111 | {name: 'Series 1', value: [1, 2, 3, 4, 5]}, 112 | {name: 'Series 2', value: [3, 4, 5, 6, 7]} 113 | ] 114 | }; 115 | ``` 116 | 117 | For pie charts, this object contains the following properties: 118 | 119 | - `series` - An array of values or objects containing values to plot. If objects are used, the following properties are supported: 120 | 121 | - `name` - A string specifying the name of the series. The name will be used in the legend and will be set as the ct:series-name attribute on the series group. 122 | 123 | - `value` - An array of values for the series. 124 | 125 | - `className` - A string to override the CSS class name for the series group. 126 | 127 | - `meta` - Meta data is serialized and written to a ct:meta attribute on the series group. 128 | 129 | Examples: 130 | 131 | ```js 132 | const data = { 133 | series: [ 15, 25 ] 134 | }; 135 | ``` 136 | 137 | ```js 138 | const data = { 139 | series: [ 140 | {name: 'Series 1', value: 15 }, 141 | {name: 'Series 2', value: 25 } 142 | ] 143 | }; 144 | ``` 145 | 146 | __Examples__ 147 | 148 | Bar: 149 | 150 | ```js 151 | co(function * () { 152 | const options = { 153 | width: 400, 154 | height: 200, 155 | axisX: { title: 'X Axis (units)' }, 156 | axisY: { title: 'Y Axis (units)' } 157 | }; 158 | 159 | const bar = yield generate('bar', options, { 160 | labels: ['a', 'b', 'c', 'd', 'e'], 161 | series: [ 162 | {name: 'Series 1', value: [1, 2, 3, 4, 5]}, 163 | {name: 'Series 2', value: [3, 4, 5, 6, 7]} 164 | ] 165 | }); 166 | }) 167 | ``` 168 | 169 | Line: 170 | 171 | ```js 172 | co(function * () { 173 | const options = { 174 | width: 400, 175 | height: 200, 176 | axisX: { title: 'X Axis (units)' }, 177 | axisY: { title: 'Y Axis (units)' } 178 | }; 179 | 180 | const line = yield generate('line', options, { 181 | labels: ['a', 'b', 'c', 'd', 'e'], 182 | series: [ 183 | {name: 'Series 1', value: [1, 2, 3, 4, 5]}, 184 | {name: 'Series 2', value: [3, 4, 5, 6, 7]} 185 | ] 186 | }); 187 | }) 188 | ``` 189 | 190 | Pie: 191 | 192 | ```js 193 | co(function * () { 194 | const options = { width: 400, height: 200 }; 195 | 196 | const pie2 = yield generate('pie', options, { 197 | series: [ 198 | {name: 'Series 1', value: 15 }, 199 | {name: 'Series 2', value: 25 } 200 | ] 201 | }); 202 | }) 203 | ``` 204 | -------------------------------------------------------------------------------- /dist/main.css: -------------------------------------------------------------------------------- 1 | .ct-double-octave:after, 2 | .ct-major-eleventh:after, 3 | .ct-major-second:after, 4 | .ct-major-seventh:after, 5 | .ct-major-sixth:after, 6 | .ct-major-tenth:after, 7 | .ct-major-third:after, 8 | .ct-major-twelfth:after, 9 | .ct-minor-second:after, 10 | .ct-minor-seventh:after, 11 | .ct-minor-sixth:after, 12 | .ct-minor-third:after, 13 | .ct-octave:after, 14 | .ct-perfect-fifth:after, 15 | .ct-perfect-fourth:after, 16 | .ct-square:after { 17 | content: ""; 18 | clear: both; 19 | } 20 | .ct-label { 21 | fill: rgba(0, 0, 0, 0.4); 22 | color: rgba(0, 0, 0, 0.4); 23 | font-size: .75rem; 24 | line-height: 1; 25 | } 26 | .ct-grid-background, 27 | .ct-line { 28 | fill: none; 29 | } 30 | .ct-chart-bar .ct-label, 31 | .ct-chart-line .ct-label { 32 | display: block; 33 | display: -webkit-box; 34 | display: -moz-box; 35 | display: -ms-flexbox; 36 | display: -webkit-flex; 37 | display: flex; 38 | } 39 | .ct-chart-donut .ct-label, 40 | .ct-chart-pie .ct-label { 41 | dominant-baseline: central; 42 | } 43 | .ct-label.ct-horizontal.ct-start { 44 | -webkit-box-align: flex-end; 45 | -webkit-align-items: flex-end; 46 | -ms-flex-align: flex-end; 47 | align-items: flex-end; 48 | -webkit-box-pack: flex-start; 49 | -webkit-justify-content: flex-start; 50 | -ms-flex-pack: flex-start; 51 | justify-content: flex-start; 52 | text-align: left; 53 | text-anchor: start; 54 | } 55 | .ct-label.ct-horizontal.ct-end { 56 | -webkit-box-align: flex-start; 57 | -webkit-align-items: flex-start; 58 | -ms-flex-align: flex-start; 59 | align-items: flex-start; 60 | -webkit-box-pack: flex-start; 61 | -webkit-justify-content: flex-start; 62 | -ms-flex-pack: flex-start; 63 | justify-content: flex-start; 64 | text-align: left; 65 | text-anchor: start; 66 | } 67 | .ct-label.ct-vertical.ct-start { 68 | -webkit-box-align: flex-end; 69 | -webkit-align-items: flex-end; 70 | -ms-flex-align: flex-end; 71 | align-items: flex-end; 72 | -webkit-box-pack: flex-end; 73 | -webkit-justify-content: flex-end; 74 | -ms-flex-pack: flex-end; 75 | justify-content: flex-end; 76 | text-align: right; 77 | text-anchor: end; 78 | } 79 | .ct-label.ct-vertical.ct-end { 80 | -webkit-box-align: flex-end; 81 | -webkit-align-items: flex-end; 82 | -ms-flex-align: flex-end; 83 | align-items: flex-end; 84 | -webkit-box-pack: flex-start; 85 | -webkit-justify-content: flex-start; 86 | -ms-flex-pack: flex-start; 87 | justify-content: flex-start; 88 | text-align: left; 89 | text-anchor: start; 90 | } 91 | .ct-chart-bar .ct-label.ct-horizontal.ct-start { 92 | -webkit-box-align: flex-end; 93 | -webkit-align-items: flex-end; 94 | -ms-flex-align: flex-end; 95 | align-items: flex-end; 96 | -webkit-box-pack: center; 97 | -webkit-justify-content: center; 98 | -ms-flex-pack: center; 99 | justify-content: center; 100 | text-align: center; 101 | text-anchor: start; 102 | } 103 | .ct-chart-bar .ct-label.ct-horizontal.ct-end { 104 | -webkit-box-align: flex-start; 105 | -webkit-align-items: flex-start; 106 | -ms-flex-align: flex-start; 107 | align-items: flex-start; 108 | -webkit-box-pack: center; 109 | -webkit-justify-content: center; 110 | -ms-flex-pack: center; 111 | justify-content: center; 112 | text-align: center; 113 | text-anchor: start; 114 | } 115 | .ct-chart-bar.ct-horizontal-bars .ct-label.ct-horizontal.ct-start { 116 | -webkit-box-align: flex-end; 117 | -webkit-align-items: flex-end; 118 | -ms-flex-align: flex-end; 119 | align-items: flex-end; 120 | -webkit-box-pack: flex-start; 121 | -webkit-justify-content: flex-start; 122 | -ms-flex-pack: flex-start; 123 | justify-content: flex-start; 124 | text-align: left; 125 | text-anchor: start; 126 | } 127 | .ct-chart-bar.ct-horizontal-bars .ct-label.ct-horizontal.ct-end { 128 | -webkit-box-align: flex-start; 129 | -webkit-align-items: flex-start; 130 | -ms-flex-align: flex-start; 131 | align-items: flex-start; 132 | -webkit-box-pack: flex-start; 133 | -webkit-justify-content: flex-start; 134 | -ms-flex-pack: flex-start; 135 | justify-content: flex-start; 136 | text-align: left; 137 | text-anchor: start; 138 | } 139 | .ct-chart-bar.ct-horizontal-bars .ct-label.ct-vertical.ct-start { 140 | -webkit-box-align: center; 141 | -webkit-align-items: center; 142 | -ms-flex-align: center; 143 | align-items: center; 144 | -webkit-box-pack: flex-end; 145 | -webkit-justify-content: flex-end; 146 | -ms-flex-pack: flex-end; 147 | justify-content: flex-end; 148 | text-align: right; 149 | text-anchor: end; 150 | } 151 | .ct-chart-bar.ct-horizontal-bars .ct-label.ct-vertical.ct-end { 152 | -webkit-box-align: center; 153 | -webkit-align-items: center; 154 | -ms-flex-align: center; 155 | align-items: center; 156 | -webkit-box-pack: flex-start; 157 | -webkit-justify-content: flex-start; 158 | -ms-flex-pack: flex-start; 159 | justify-content: flex-start; 160 | text-align: left; 161 | text-anchor: end; 162 | } 163 | .ct-grid { 164 | stroke: rgba(0, 0, 0, 0.2); 165 | stroke-width: 1px; 166 | stroke-dasharray: 2px; 167 | } 168 | .ct-point { 169 | stroke-width: 10px; 170 | stroke-linecap: round; 171 | } 172 | .ct-line { 173 | stroke-width: 4px; 174 | } 175 | .ct-area { 176 | stroke: none; 177 | fill-opacity: 0.1; 178 | } 179 | .ct-bar { 180 | fill: none; 181 | stroke-width: 10px; 182 | } 183 | .ct-slice-donut { 184 | fill: none; 185 | stroke-width: 60px; 186 | } 187 | .ct-series-a .ct-bar, 188 | .ct-series-a .ct-line, 189 | .ct-series-a .ct-point, 190 | .ct-series-a .ct-slice-donut { 191 | stroke: #d70206; 192 | } 193 | .ct-series-a .ct-area, 194 | .ct-series-a .ct-slice-donut-solid, 195 | .ct-series-a .ct-slice-pie { 196 | fill: #d70206; 197 | } 198 | .ct-series-b .ct-bar, 199 | .ct-series-b .ct-line, 200 | .ct-series-b .ct-point, 201 | .ct-series-b .ct-slice-donut { 202 | stroke: #f05b4f; 203 | } 204 | .ct-series-b .ct-area, 205 | .ct-series-b .ct-slice-donut-solid, 206 | .ct-series-b .ct-slice-pie { 207 | fill: #f05b4f; 208 | } 209 | .ct-series-c .ct-bar, 210 | .ct-series-c .ct-line, 211 | .ct-series-c .ct-point, 212 | .ct-series-c .ct-slice-donut { 213 | stroke: #f4c63d; 214 | } 215 | .ct-series-c .ct-area, 216 | .ct-series-c .ct-slice-donut-solid, 217 | .ct-series-c .ct-slice-pie { 218 | fill: #f4c63d; 219 | } 220 | .ct-series-d .ct-bar, 221 | .ct-series-d .ct-line, 222 | .ct-series-d .ct-point, 223 | .ct-series-d .ct-slice-donut { 224 | stroke: #d17905; 225 | } 226 | .ct-series-d .ct-area, 227 | .ct-series-d .ct-slice-donut-solid, 228 | .ct-series-d .ct-slice-pie { 229 | fill: #d17905; 230 | } 231 | .ct-series-e .ct-bar, 232 | .ct-series-e .ct-line, 233 | .ct-series-e .ct-point, 234 | .ct-series-e .ct-slice-donut { 235 | stroke: #453d3f; 236 | } 237 | .ct-series-e .ct-area, 238 | .ct-series-e .ct-slice-donut-solid, 239 | .ct-series-e .ct-slice-pie { 240 | fill: #453d3f; 241 | } 242 | .ct-series-f .ct-bar, 243 | .ct-series-f .ct-line, 244 | .ct-series-f .ct-point, 245 | .ct-series-f .ct-slice-donut { 246 | stroke: #59922b; 247 | } 248 | .ct-series-f .ct-area, 249 | .ct-series-f .ct-slice-donut-solid, 250 | .ct-series-f .ct-slice-pie { 251 | fill: #59922b; 252 | } 253 | .ct-series-g .ct-bar, 254 | .ct-series-g .ct-line, 255 | .ct-series-g .ct-point, 256 | .ct-series-g .ct-slice-donut { 257 | stroke: #0544d3; 258 | } 259 | .ct-series-g .ct-area, 260 | .ct-series-g .ct-slice-donut-solid, 261 | .ct-series-g .ct-slice-pie { 262 | fill: #0544d3; 263 | } 264 | .ct-series-h .ct-bar, 265 | .ct-series-h .ct-line, 266 | .ct-series-h .ct-point, 267 | .ct-series-h .ct-slice-donut { 268 | stroke: #6b0392; 269 | } 270 | .ct-series-h .ct-area, 271 | .ct-series-h .ct-slice-donut-solid, 272 | .ct-series-h .ct-slice-pie { 273 | fill: #6b0392; 274 | } 275 | .ct-series-i .ct-bar, 276 | .ct-series-i .ct-line, 277 | .ct-series-i .ct-point, 278 | .ct-series-i .ct-slice-donut { 279 | stroke: #f05b4f; 280 | } 281 | .ct-series-i .ct-area, 282 | .ct-series-i .ct-slice-donut-solid, 283 | .ct-series-i .ct-slice-pie { 284 | fill: #f05b4f; 285 | } 286 | .ct-series-j .ct-bar, 287 | .ct-series-j .ct-line, 288 | .ct-series-j .ct-point, 289 | .ct-series-j .ct-slice-donut { 290 | stroke: #dda458; 291 | } 292 | .ct-series-j .ct-area, 293 | .ct-series-j .ct-slice-donut-solid, 294 | .ct-series-j .ct-slice-pie { 295 | fill: #dda458; 296 | } 297 | .ct-series-k .ct-bar, 298 | .ct-series-k .ct-line, 299 | .ct-series-k .ct-point, 300 | .ct-series-k .ct-slice-donut { 301 | stroke: #eacf7d; 302 | } 303 | .ct-series-k .ct-area, 304 | .ct-series-k .ct-slice-donut-solid, 305 | .ct-series-k .ct-slice-pie { 306 | fill: #eacf7d; 307 | } 308 | .ct-series-l .ct-bar, 309 | .ct-series-l .ct-line, 310 | .ct-series-l .ct-point, 311 | .ct-series-l .ct-slice-donut { 312 | stroke: #86797d; 313 | } 314 | .ct-series-l .ct-area, 315 | .ct-series-l .ct-slice-donut-solid, 316 | .ct-series-l .ct-slice-pie { 317 | fill: #86797d; 318 | } 319 | .ct-series-m .ct-bar, 320 | .ct-series-m .ct-line, 321 | .ct-series-m .ct-point, 322 | .ct-series-m .ct-slice-donut { 323 | stroke: #b2c326; 324 | } 325 | .ct-series-m .ct-area, 326 | .ct-series-m .ct-slice-donut-solid, 327 | .ct-series-m .ct-slice-pie { 328 | fill: #b2c326; 329 | } 330 | .ct-series-n .ct-bar, 331 | .ct-series-n .ct-line, 332 | .ct-series-n .ct-point, 333 | .ct-series-n .ct-slice-donut { 334 | stroke: #6188e2; 335 | } 336 | .ct-series-n .ct-area, 337 | .ct-series-n .ct-slice-donut-solid, 338 | .ct-series-n .ct-slice-pie { 339 | fill: #6188e2; 340 | } 341 | .ct-series-o .ct-bar, 342 | .ct-series-o .ct-line, 343 | .ct-series-o .ct-point, 344 | .ct-series-o .ct-slice-donut { 345 | stroke: #a748ca; 346 | } 347 | .ct-series-o .ct-area, 348 | .ct-series-o .ct-slice-donut-solid, 349 | .ct-series-o .ct-slice-pie { 350 | fill: #a748ca; 351 | } 352 | .ct-square { 353 | display: block; 354 | position: relative; 355 | width: 100%; 356 | } 357 | .ct-square:before { 358 | display: block; 359 | float: left; 360 | content: ""; 361 | width: 0; 362 | height: 0; 363 | padding-bottom: 100%; 364 | } 365 | .ct-square:after { 366 | display: table; 367 | } 368 | .ct-square > svg { 369 | display: block; 370 | position: absolute; 371 | top: 0; 372 | left: 0; 373 | } 374 | .ct-minor-second { 375 | display: block; 376 | position: relative; 377 | width: 100%; 378 | } 379 | .ct-minor-second:before { 380 | display: block; 381 | float: left; 382 | content: ""; 383 | width: 0; 384 | height: 0; 385 | padding-bottom: 93.75%; 386 | } 387 | .ct-minor-second:after { 388 | display: table; 389 | } 390 | .ct-minor-second > svg { 391 | display: block; 392 | position: absolute; 393 | top: 0; 394 | left: 0; 395 | } 396 | .ct-major-second { 397 | display: block; 398 | position: relative; 399 | width: 100%; 400 | } 401 | .ct-major-second:before { 402 | display: block; 403 | float: left; 404 | content: ""; 405 | width: 0; 406 | height: 0; 407 | padding-bottom: 88.88888889%; 408 | } 409 | .ct-major-second:after { 410 | display: table; 411 | } 412 | .ct-major-second > svg { 413 | display: block; 414 | position: absolute; 415 | top: 0; 416 | left: 0; 417 | } 418 | .ct-minor-third { 419 | display: block; 420 | position: relative; 421 | width: 100%; 422 | } 423 | .ct-minor-third:before { 424 | display: block; 425 | float: left; 426 | content: ""; 427 | width: 0; 428 | height: 0; 429 | padding-bottom: 83.33333333%; 430 | } 431 | .ct-minor-third:after { 432 | display: table; 433 | } 434 | .ct-minor-third > svg { 435 | display: block; 436 | position: absolute; 437 | top: 0; 438 | left: 0; 439 | } 440 | .ct-major-third { 441 | display: block; 442 | position: relative; 443 | width: 100%; 444 | } 445 | .ct-major-third:before { 446 | display: block; 447 | float: left; 448 | content: ""; 449 | width: 0; 450 | height: 0; 451 | padding-bottom: 80%; 452 | } 453 | .ct-major-third:after { 454 | display: table; 455 | } 456 | .ct-major-third > svg { 457 | display: block; 458 | position: absolute; 459 | top: 0; 460 | left: 0; 461 | } 462 | .ct-perfect-fourth { 463 | display: block; 464 | position: relative; 465 | width: 100%; 466 | } 467 | .ct-perfect-fourth:before { 468 | display: block; 469 | float: left; 470 | content: ""; 471 | width: 0; 472 | height: 0; 473 | padding-bottom: 75%; 474 | } 475 | .ct-perfect-fourth:after { 476 | display: table; 477 | } 478 | .ct-perfect-fourth > svg { 479 | display: block; 480 | position: absolute; 481 | top: 0; 482 | left: 0; 483 | } 484 | .ct-perfect-fifth { 485 | display: block; 486 | position: relative; 487 | width: 100%; 488 | } 489 | .ct-perfect-fifth:before { 490 | display: block; 491 | float: left; 492 | content: ""; 493 | width: 0; 494 | height: 0; 495 | padding-bottom: 66.66666667%; 496 | } 497 | .ct-perfect-fifth:after { 498 | display: table; 499 | } 500 | .ct-perfect-fifth > svg { 501 | display: block; 502 | position: absolute; 503 | top: 0; 504 | left: 0; 505 | } 506 | .ct-minor-sixth { 507 | display: block; 508 | position: relative; 509 | width: 100%; 510 | } 511 | .ct-minor-sixth:before { 512 | display: block; 513 | float: left; 514 | content: ""; 515 | width: 0; 516 | height: 0; 517 | padding-bottom: 62.5%; 518 | } 519 | .ct-minor-sixth:after { 520 | display: table; 521 | } 522 | .ct-minor-sixth > svg { 523 | display: block; 524 | position: absolute; 525 | top: 0; 526 | left: 0; 527 | } 528 | .ct-golden-section { 529 | display: block; 530 | position: relative; 531 | width: 100%; 532 | } 533 | .ct-golden-section:before { 534 | display: block; 535 | float: left; 536 | content: ""; 537 | width: 0; 538 | height: 0; 539 | padding-bottom: 61.80469716%; 540 | } 541 | .ct-golden-section:after { 542 | content: ""; 543 | display: table; 544 | clear: both; 545 | } 546 | .ct-golden-section > svg { 547 | display: block; 548 | position: absolute; 549 | top: 0; 550 | left: 0; 551 | } 552 | .ct-major-sixth { 553 | display: block; 554 | position: relative; 555 | width: 100%; 556 | } 557 | .ct-major-sixth:before { 558 | display: block; 559 | float: left; 560 | content: ""; 561 | width: 0; 562 | height: 0; 563 | padding-bottom: 60%; 564 | } 565 | .ct-major-sixth:after { 566 | display: table; 567 | } 568 | .ct-major-sixth > svg { 569 | display: block; 570 | position: absolute; 571 | top: 0; 572 | left: 0; 573 | } 574 | .ct-minor-seventh { 575 | display: block; 576 | position: relative; 577 | width: 100%; 578 | } 579 | .ct-minor-seventh:before { 580 | display: block; 581 | float: left; 582 | content: ""; 583 | width: 0; 584 | height: 0; 585 | padding-bottom: 56.25%; 586 | } 587 | .ct-minor-seventh:after { 588 | display: table; 589 | } 590 | .ct-minor-seventh > svg { 591 | display: block; 592 | position: absolute; 593 | top: 0; 594 | left: 0; 595 | } 596 | .ct-major-seventh { 597 | display: block; 598 | position: relative; 599 | width: 100%; 600 | } 601 | .ct-major-seventh:before { 602 | display: block; 603 | float: left; 604 | content: ""; 605 | width: 0; 606 | height: 0; 607 | padding-bottom: 53.33333333%; 608 | } 609 | .ct-major-seventh:after { 610 | display: table; 611 | } 612 | .ct-major-seventh > svg { 613 | display: block; 614 | position: absolute; 615 | top: 0; 616 | left: 0; 617 | } 618 | .ct-octave { 619 | display: block; 620 | position: relative; 621 | width: 100%; 622 | } 623 | .ct-octave:before { 624 | display: block; 625 | float: left; 626 | content: ""; 627 | width: 0; 628 | height: 0; 629 | padding-bottom: 50%; 630 | } 631 | .ct-octave:after { 632 | display: table; 633 | } 634 | .ct-octave > svg { 635 | display: block; 636 | position: absolute; 637 | top: 0; 638 | left: 0; 639 | } 640 | .ct-major-tenth { 641 | display: block; 642 | position: relative; 643 | width: 100%; 644 | } 645 | .ct-major-tenth:before { 646 | display: block; 647 | float: left; 648 | content: ""; 649 | width: 0; 650 | height: 0; 651 | padding-bottom: 40%; 652 | } 653 | .ct-major-tenth:after { 654 | display: table; 655 | } 656 | .ct-major-tenth > svg { 657 | display: block; 658 | position: absolute; 659 | top: 0; 660 | left: 0; 661 | } 662 | .ct-major-eleventh { 663 | display: block; 664 | position: relative; 665 | width: 100%; 666 | } 667 | .ct-major-eleventh:before { 668 | display: block; 669 | float: left; 670 | content: ""; 671 | width: 0; 672 | height: 0; 673 | padding-bottom: 37.5%; 674 | } 675 | .ct-major-eleventh:after { 676 | display: table; 677 | } 678 | .ct-major-eleventh > svg { 679 | display: block; 680 | position: absolute; 681 | top: 0; 682 | left: 0; 683 | } 684 | .ct-major-twelfth { 685 | display: block; 686 | position: relative; 687 | width: 100%; 688 | } 689 | .ct-major-twelfth:before { 690 | display: block; 691 | float: left; 692 | content: ""; 693 | width: 0; 694 | height: 0; 695 | padding-bottom: 33.33333333%; 696 | } 697 | .ct-major-twelfth:after { 698 | display: table; 699 | } 700 | .ct-major-twelfth > svg { 701 | display: block; 702 | position: absolute; 703 | top: 0; 704 | left: 0; 705 | } 706 | .ct-double-octave { 707 | display: block; 708 | position: relative; 709 | width: 100%; 710 | } 711 | .ct-double-octave:before { 712 | display: block; 713 | float: left; 714 | content: ""; 715 | width: 0; 716 | height: 0; 717 | padding-bottom: 25%; 718 | } 719 | .ct-double-octave:after { 720 | display: table; 721 | } 722 | .ct-double-octave > svg { 723 | display: block; 724 | position: absolute; 725 | top: 0; 726 | left: 0; 727 | } 728 | .ct-legend { 729 | margin-top: 1em; 730 | } 731 | .ct-legend .ct-variable { 732 | display: inline-block; 733 | margin: 0 0.1em; 734 | } 735 | .ct-legend .ct-variable .ct-swatch { 736 | display: inline-block; 737 | vertical-align: baseline; 738 | margin-right: 0.3em; 739 | width: 0.5em; 740 | height: 0.5em; 741 | border: 1px solid white; 742 | border-radius: 0.14em; 743 | } 744 | .ct-series-a .ct-bar, 745 | .ct-series-a .ct-line, 746 | .ct-series-a .ct-point, 747 | .ct-series-a .ct-slice-donut, 748 | .ct-series-a .ct-swatch { 749 | background-color: skyblue; 750 | stroke: skyblue; 751 | } 752 | .ct-series-a .ct-area, 753 | .ct-series-a .ct-slice-pie { 754 | fill: skyblue; 755 | } 756 | .ct-series-b .ct-bar, 757 | .ct-series-b .ct-line, 758 | .ct-series-b .ct-point, 759 | .ct-series-b .ct-slice-donut, 760 | .ct-series-b .ct-swatch { 761 | background-color: limegreen; 762 | stroke: limegreen; 763 | } 764 | .ct-series-b .ct-area, 765 | .ct-series-b .ct-slice-pie { 766 | fill: limegreen; 767 | } 768 | .ct-series-c .ct-bar, 769 | .ct-series-c .ct-line, 770 | .ct-series-c .ct-point, 771 | .ct-series-c .ct-slice-donut, 772 | .ct-series-c .ct-swatch { 773 | background-color: indianred; 774 | stroke: indianred; 775 | } 776 | .ct-series-c .ct-area, 777 | .ct-series-c .ct-slice-pie { 778 | fill: indianred; 779 | } 780 | .ct-series-d .ct-bar, 781 | .ct-series-d .ct-line, 782 | .ct-series-d .ct-point, 783 | .ct-series-d .ct-slice-donut, 784 | .ct-series-d .ct-swatch { 785 | background-color: gold; 786 | stroke: gold; 787 | } 788 | .ct-series-d .ct-area, 789 | .ct-series-d .ct-slice-pie { 790 | fill: gold; 791 | } 792 | .ct-series-e .ct-bar, 793 | .ct-series-e .ct-line, 794 | .ct-series-e .ct-point, 795 | .ct-series-e .ct-slice-donut, 796 | .ct-series-e .ct-swatch { 797 | background-color: orange; 798 | stroke: orange; 799 | } 800 | .ct-series-e .ct-area, 801 | .ct-series-e .ct-slice-pie { 802 | fill: orange; 803 | } 804 | .ct-series-f .ct-bar, 805 | .ct-series-f .ct-line, 806 | .ct-series-f .ct-point, 807 | .ct-series-f .ct-slice-donut, 808 | .ct-series-f .ct-swatch { 809 | background-color: indianred; 810 | stroke: indianred; 811 | } 812 | .ct-series-f .ct-area, 813 | .ct-series-f .ct-slice-pie { 814 | fill: indianred; 815 | } 816 | .ct-series-g .ct-bar, 817 | .ct-series-g .ct-line, 818 | .ct-series-g .ct-point, 819 | .ct-series-g .ct-slice-donut, 820 | .ct-series-g .ct-swatch { 821 | background-color: indianred; 822 | stroke: indianred; 823 | } 824 | .ct-series-g .ct-area, 825 | .ct-series-g .ct-slice-pie { 826 | fill: indianred; 827 | } 828 | .ct-series-h .ct-bar, 829 | .ct-series-h .ct-line, 830 | .ct-series-h .ct-point, 831 | .ct-series-h .ct-slice-donut, 832 | .ct-series-h .ct-swatch { 833 | background-color: indianred; 834 | stroke: indianred; 835 | } 836 | .ct-series-h .ct-area, 837 | .ct-series-h .ct-slice-pie { 838 | fill: indianred; 839 | } 840 | .ct-series-i .ct-bar, 841 | .ct-series-i .ct-line, 842 | .ct-series-i .ct-point, 843 | .ct-series-i .ct-slice-donut, 844 | .ct-series-i .ct-swatch { 845 | background-color: indianred; 846 | stroke: indianred; 847 | } 848 | .ct-series-i .ct-area, 849 | .ct-series-i .ct-slice-pie { 850 | fill: indianred; 851 | } 852 | .ct-series-j .ct-bar, 853 | .ct-series-j .ct-line, 854 | .ct-series-j .ct-point, 855 | .ct-series-j .ct-slice-donut, 856 | .ct-series-j .ct-swatch { 857 | background-color: indianred; 858 | stroke: indianred; 859 | } 860 | .ct-series-j .ct-area, 861 | .ct-series-j .ct-slice-pie { 862 | fill: indianred; 863 | } 864 | .ct-series-k .ct-bar, 865 | .ct-series-k .ct-line, 866 | .ct-series-k .ct-point, 867 | .ct-series-k .ct-slice-donut, 868 | .ct-series-k .ct-swatch { 869 | background-color: indianred; 870 | stroke: indianred; 871 | } 872 | .ct-series-k .ct-area, 873 | .ct-series-k .ct-slice-pie { 874 | fill: indianred; 875 | } 876 | .ct-series-l .ct-bar, 877 | .ct-series-l .ct-line, 878 | .ct-series-l .ct-point, 879 | .ct-series-l .ct-slice-donut, 880 | .ct-series-l .ct-swatch { 881 | background-color: indianred; 882 | stroke: indianred; 883 | } 884 | .ct-series-l .ct-area, 885 | .ct-series-l .ct-slice-pie { 886 | fill: indianred; 887 | } 888 | .ct-series-m .ct-bar, 889 | .ct-series-m .ct-line, 890 | .ct-series-m .ct-point, 891 | .ct-series-m .ct-slice-donut, 892 | .ct-series-m .ct-swatch { 893 | background-color: indianred; 894 | stroke: indianred; 895 | } 896 | .ct-series-m .ct-area, 897 | .ct-series-m .ct-slice-pie { 898 | fill: indianred; 899 | } 900 | .ct-series-n .ct-bar, 901 | .ct-series-n .ct-line, 902 | .ct-series-n .ct-point, 903 | .ct-series-n .ct-slice-donut, 904 | .ct-series-n .ct-swatch { 905 | background-color: indianred; 906 | stroke: indianred; 907 | } 908 | .ct-series-n .ct-area, 909 | .ct-series-n .ct-slice-pie { 910 | fill: indianred; 911 | } 912 | .ct-series-o .ct-bar, 913 | .ct-series-o .ct-line, 914 | .ct-series-o .ct-point, 915 | .ct-series-o .ct-slice-donut, 916 | .ct-series-o .ct-swatch { 917 | background-color: indianred; 918 | stroke: indianred; 919 | } 920 | .ct-series-o .ct-area, 921 | .ct-series-o .ct-slice-pie { 922 | fill: indianred; 923 | } 924 | -------------------------------------------------------------------------------- /lib/axis-title.js: -------------------------------------------------------------------------------- 1 | var common = { 2 | class: 'ct-axis-title ct-label', 3 | textAnchor: 'middle' 4 | }; 5 | var getXConfig = (chart) => { 6 | var x = 7 | chart.options.chartPadding.left + 8 | (chart.options.axisY.position === 'start' ? chart.options.axisY.offset : 0) + 9 | (chart.axisX.axisLength / 2); 10 | var y = 11 | chart.options.chartPadding.top + 12 | (chart.options.axisX.position === 'start' ? 0 : chart.axisY.axisLength + chart.options.axisX.offset); 13 | var dominantBaseline = (chart.options.axisX.position === 'start' ? 'hanging' : 'text-after-edge'); 14 | return { 15 | class: common.class, 16 | text: chart.options.axisX.title, 17 | attr: { x, y, 'dominant-baseline': dominantBaseline, 'text-anchor': common.textAnchor } 18 | }; 19 | }; 20 | var getYConfig = (chart) => { 21 | var x = 22 | chart.options.chartPadding.left + 23 | (chart.options.axisY.position === 'start' ? 0 : chart.axisX.axisLength + chart.options.axisY.offset); 24 | var y = 25 | chart.options.chartPadding.top + 26 | (chart.options.axisX.position === 'start' ? chart.options.axisX.offset : 0) + 27 | (chart.axisY.axisLength / 2); 28 | var rotation = (chart.options.axisY.position === 'start' ? -90 : 90); 29 | var transform = `rotate(${rotation}, ${x}, ${y})`; 30 | var dominantBaseline = (chart.options.axisY.position === 'start' ? 'hanging' : 'text-after-edge'); 31 | return { 32 | class: common.class, 33 | text: chart.options.axisY.title, 34 | attr: { x, y, transform, 'dominant-baseline': dominantBaseline, 'text-anchor': common.textAnchor } 35 | }; 36 | }; 37 | var createText = (Chartist, config) => { 38 | var text = new Chartist.Svg("text"); 39 | text.addClass(config.class); 40 | text.attr(config.attr); 41 | text.text(config.text); 42 | return text; 43 | }; 44 | 45 | var render = function(Chartist, chart) { 46 | if (chart.axisX && chart.options.axisX.title) { 47 | var xConfig = getXConfig(chart); 48 | var xTitle = createText(Chartist, xConfig); 49 | chart.svg.append(xTitle); 50 | } 51 | if (chart.axisY && chart.options.axisY.title) { 52 | var yConfig = getYConfig(chart); 53 | var yTitle = createText(Chartist, yConfig); 54 | chart.svg.append(yTitle); 55 | } 56 | }; 57 | 58 | module.exports = render; 59 | -------------------------------------------------------------------------------- /lib/chart.js: -------------------------------------------------------------------------------- 1 | const capitalize = require('underscore.string/capitalize'); 2 | const co = require('co'); 3 | const renderAxisTitles = require('./axis-title'); 4 | 5 | /** 6 | * Generate Chart HTML 7 | * @param {String} type 8 | * bar, line, pie 9 | * @return {Promise{String}} html 10 | */ 11 | const generate = co.wrap(function * (Chartist, window, type, options, data) { 12 | type = capitalize(type); 13 | if (!Chartist[type]) throw new TypeError(`Unsupported chart type: ${type}`); 14 | const container = window.document.createElement('div'); 15 | const chart = new Chartist[type](container, data, options); 16 | const event = yield new Promise(resolve => chart.on('created', resolve)); 17 | chart.axisX = event.axisX; 18 | chart.axisY = event.axisY; 19 | renderAxisTitles(Chartist, chart); 20 | chart.detach(); 21 | return container.innerHTML; 22 | }); 23 | 24 | module.exports = generate; 25 | -------------------------------------------------------------------------------- /lib/chartist.js: -------------------------------------------------------------------------------- 1 | const co = require('co'); 2 | const fs = require('fs'); 3 | const jsdom = require('jsdom'); 4 | const matchMedia = require('matchmedia'); 5 | const path = require('path'); 6 | const R = require('ramda'); 7 | const vm = require('vm'); 8 | 9 | 10 | var chartistSource; 11 | try { 12 | // NPM 2: nested dependency resolution 13 | chartistSource = fs.readFileSync(path.resolve(__dirname, '../node_modules/chartist/dist/chartist.js')); 14 | } catch (error) { 15 | try { 16 | // NPM 3+: flat dependency resolution 17 | chartistSource = fs.readFileSync(path.resolve(__dirname, '../../chartist/dist/chartist.js')); 18 | } catch (error) { 19 | throw error; // Chartist module not found! 20 | } 21 | } 22 | 23 | 24 | const createWindow = co.wrap(function * () { 25 | const window = yield new Promise((resolve, reject) => { 26 | jsdom.env({ 27 | html: "", 28 | done: function (error, window) { error ? reject(error) : resolve(window) } 29 | }) 30 | }); 31 | // matchMedia: required polyfill 32 | if (!window.matchMedia) window.matchMedia = matchMedia; 33 | // Array: must be overriden so that Chartist can use `instanceof Array` 34 | // within its context to check whether arguments created in this context 35 | // are Array's. 36 | window.Array = Array; 37 | return window; 38 | }); 39 | const loadChartist = window => { 40 | vm.runInNewContext(chartistSource, window); 41 | const Chartist = window.Chartist; 42 | // Disable foreignObject and animations (incompatible with PrinceXML) 43 | const isSupported = Chartist.Svg.isSupported; 44 | Chartist.Svg.isSupported = feature => 45 | R.contains(feature, ['Extensibility', 'AnimationEventsAttribute']) ? false : isSupported(feature); 46 | return Chartist; 47 | }; 48 | const initialize = co.wrap(function * () { 49 | const window = yield createWindow(); 50 | const Chartist = loadChartist(window); 51 | return { window, Chartist }; 52 | }); 53 | 54 | module.exports = { initialize }; 55 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const co = require('co'); 2 | const chartist = require('./chartist'); 3 | const generateChart = require('./chart'); 4 | const generateLegend = require('./legend'); 5 | const is = require('is_js'); 6 | const R = require('ramda'); 7 | const Ru = require('@panosoft/ramda-utils'); 8 | 9 | /** 10 | * Generate Chart HTML 11 | * 12 | * @param {String} type 13 | * bar, line, pie 14 | * @param {Object} options 15 | * Chartist options + axis*.title 16 | * @param {Object} data 17 | * Chartist data object 18 | * 19 | * @return {Promise{String}} svg 20 | */ 21 | const generate = R.curryN(3, co.wrap(function * (type, options, data) { 22 | const environment = yield chartist.initialize(); 23 | const window = environment.window; 24 | const Chartist = environment.Chartist; 25 | // process options 26 | options = is.function(options) ? options(Chartist) : options; 27 | if (is.not.json(options)) throw new TypeError('options must be an object or a function that returns an object.'); 28 | options = Ru.defaults({ legend: true }, options); 29 | // create chart 30 | const chart = yield generateChart(Chartist, window, type, options, data); 31 | const legend = options.legend ? generateLegend(data) : ''; 32 | return `