├── .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 | [![Travis](https://img.shields.io/travis/panosoft/node-chartist.svg)](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 `
${chart}${legend}
`; 33 | })); 34 | 35 | module.exports = generate; 36 | -------------------------------------------------------------------------------- /lib/legend.js: -------------------------------------------------------------------------------- 1 | var R = require('ramda'); 2 | 3 | var mapIndexed = R.addIndex(R.map); 4 | 5 | var labels = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o']; 6 | var variable = (label, name) => ` 7 |
8 | ${name} 9 |
10 | `; 11 | var legend = (variables) => ` 12 |
13 | ${variables} 14 |
15 | `; 16 | var generate = (data) => { 17 | var series = data.series; 18 | var variables = R.pipe( 19 | R.map(R.prop('name')), 20 | mapIndexed((name, index) => name ? variable(labels[index], name) : null), 21 | R.reject(R.isNil), 22 | R.join('') 23 | )(series); 24 | return legend(variables); 25 | }; 26 | 27 | module.exports = generate; 28 | -------------------------------------------------------------------------------- /lib/main.less: -------------------------------------------------------------------------------- 1 | @import (less) '../node_modules/chartist/dist/chartist.min.css'; 2 | 3 | // Legend 4 | .ct-legend { 5 | margin-top: 1em; 6 | .ct-variable { 7 | display: inline-block; 8 | margin: 0 0.1em; 9 | .ct-swatch { 10 | display: inline-block; 11 | vertical-align: baseline; 12 | margin-right: 0.3em; 13 | width: 0.5em; 14 | height: 0.5em; 15 | border: 1px solid white; 16 | border-radius: 0.14em; 17 | } 18 | } 19 | } 20 | 21 | // Color Palette 22 | .ct-series(@label, @color) { 23 | .ct-series-@{label} { 24 | .ct-bar, .ct-line, .ct-point, .ct-slice-donut, .ct-swatch { background-color: @color; stroke: @color; } 25 | .ct-area, .ct-slice-pie { fill: @color; } 26 | } 27 | } 28 | .ct-series(a, skyblue); 29 | .ct-series(b, limegreen); 30 | .ct-series(c, indianred); 31 | .ct-series(d, gold); 32 | .ct-series(e, orange); 33 | .ct-series(f, indianred); 34 | .ct-series(g, indianred); 35 | .ct-series(h, indianred); 36 | .ct-series(i, indianred); 37 | .ct-series(j, indianred); 38 | .ct-series(k, indianred); 39 | .ct-series(l, indianred); 40 | .ct-series(m, indianred); 41 | .ct-series(n, indianred); 42 | .ct-series(o, indianred); 43 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-chartist", 3 | "version": "1.0.5", 4 | "description": "SVG Charts on the server.", 5 | "author": "", 6 | "license": "Unlicense", 7 | "main": "lib/index.js", 8 | "browserify": { 9 | "transform": [ 10 | "brfs" 11 | ] 12 | }, 13 | "dependencies": { 14 | "@panosoft/ramda-utils": "^0.2.8", 15 | "brfs": "^1.4.3", 16 | "chartist": "^0.11.0", 17 | "chartist-plugin-axistitle": "0.0.1", 18 | "co": "^4.6.0", 19 | "is_js": "^0.9.0", 20 | "jsdom": "^9.0.0", 21 | "matchmedia": "^0.1.2", 22 | "promisify-node": "^0.2.1", 23 | "ramda": "^0.18.0", 24 | "underscore.string": "^3.3.4" 25 | }, 26 | "devDependencies": { 27 | "chai": "^3.4.1", 28 | "chai-as-promised": "^5.1.0", 29 | "cheerio": "^0.19.0", 30 | "less": "^2.7.1", 31 | "mocha": "^2.3.4", 32 | "rimraf": "^2.5.3", 33 | "sinon": "^1.17.7", 34 | "sinon-chai": "^2.8.0", 35 | "touch": "^1.0.0" 36 | }, 37 | "scripts": { 38 | "prebuild": "rimraf dist/*", 39 | "build": "lessc lib/main.less dist/main.css && touch node_modules/canvas.js", 40 | "pretest": "npm run build", 41 | "test": "mocha", 42 | "watch": "watch 'npm run build' ./lib ./node_modules" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const cheerio = require('cheerio'); 2 | const co = require('co'); 3 | const expect = require('chai') 4 | .use(require('chai-as-promised')) 5 | .use(require('sinon-chai')) 6 | .expect; 7 | const fs = require('fs'); 8 | const generate = require('../lib'); 9 | const path = require('path'); 10 | const sinon = require('sinon'); 11 | 12 | var exists = path => new Promise((resolve, reject) => { 13 | fs.stat(path, (error) => { 14 | if (!error) resolve(true); 15 | else if (error.code === 'ENOENT') resolve(false); 16 | else reject(error); 17 | }); 18 | }); 19 | 20 | describe('build', () => { 21 | it('create dist/main.css', () => { 22 | var filename = path.resolve(__dirname, '../dist/main.css'); 23 | return expect(exists(filename)).to.eventually.be.true; 24 | }); 25 | }); 26 | describe('install', () => { 27 | it('create node_modules/canvas.js', () => { 28 | var filename = path.resolve(__dirname, '../node_modules/canvas.js'); 29 | return expect(exists(filename)).to.eventually.be.true; 30 | }); 31 | }); 32 | describe('generate', () => { 33 | describe('chart', () => { 34 | // bar 35 | it('bar', () => co(function * () { 36 | var chart = yield generate('bar', {}, { 37 | labels: ['a', 'b', 'c'], 38 | series: [[1, 2, 3]] 39 | }); 40 | var $ = cheerio.load(chart); 41 | expect($('svg.ct-chart-bar').length).to.equal(1); 42 | })); 43 | it('line', () => co(function * () { 44 | var chart = yield generate('line', {}, { 45 | labels: ['a', 'b', 'c'], 46 | series: [[1, 2, 3]] 47 | }); 48 | var $ = cheerio.load(chart); 49 | expect($('svg.ct-chart-line').length).to.equal(1); 50 | })); 51 | it('pie', () => co(function * () { 52 | var chart = yield generate('pie', {}, { 53 | series: [1, 2, 3] 54 | }); 55 | var $ = cheerio.load(chart); 56 | expect($('svg.ct-chart-pie').length).to.equal(1); 57 | })); 58 | it('throw on unsupported type', () => 59 | expect(generate('spider', {}, {})).to.eventually.be.rejectedWith(TypeError, /Unsupported chart type/) 60 | ); 61 | }); 62 | describe('options', () => { 63 | it('object', () => co(function * () { 64 | var chart = yield generate('bar', {}, { 65 | labels: ['a', 'b', 'c'], 66 | series: [[1, 2, 3]] 67 | }); 68 | var $ = cheerio.load(chart); 69 | expect($('svg.ct-chart-bar').length).to.equal(1); 70 | })); 71 | it('function called with Chartist object', () => co(function * () { 72 | const options = sinon.stub().returns({}); 73 | var chart = yield generate('bar', options, { 74 | labels: ['a', 'b', 'c'], 75 | series: [[1, 2, 3]] 76 | }); 77 | expect(options).to.be.calledOnce 78 | .and.calledWith(sinon.match.has('AutoScaleAxis')); 79 | })); 80 | it('function must return object', () => { 81 | const options = (Chartist) => null; 82 | var chart = generate('bar', options, { 83 | labels: ['a', 'b', 'c'], 84 | series: [[1, 2, 3]] 85 | }); 86 | return expect(chart).to.eventually.be.rejectedWith(TypeError); 87 | }); 88 | }); 89 | describe('axisTitle', () => { 90 | it('x axis', () => co(function * () { 91 | var title = 'X Axis (units)'; 92 | var chart = yield generate('bar', { 93 | axisX: { title } 94 | }, { 95 | labels: ['a', 'b', 'c'], 96 | series: [[1, 2, 3]] 97 | }); 98 | var $ = cheerio.load(chart); 99 | expect($('text.ct-axis-title').length).to.equal(1); 100 | })); 101 | it('y axis', () => co(function * () { 102 | var title = 'Y Axis (units)'; 103 | var chart = yield generate('bar', { 104 | axisY: { title } 105 | }, { 106 | labels: ['a', 'b', 'c'], 107 | series: [[1, 2, 3]] 108 | }); 109 | var $ = cheerio.load(chart); 110 | expect($('text.ct-axis-title').length).to.equal(1); 111 | })); 112 | }); 113 | describe('legend', () => { 114 | it('enabled by default', () => co(function * () { 115 | var name = 'Y Axis (units)'; 116 | var options = {}; 117 | var chart = yield generate('bar', options, { 118 | labels: ['a', 'b', 'c'], 119 | series: [{ name, value: [1, 2, 3] }] 120 | }); 121 | var $ = cheerio.load(chart); 122 | expect($('.ct-legend .ct-variable').length).to.equal(1); 123 | })); 124 | it('can be disabled', () => co(function * () { 125 | var name = 'Y Axis (units)'; 126 | var options = { legend: false }; 127 | var chart = yield generate('bar', options, { 128 | labels: ['a', 'b', 'c'], 129 | series: [{ name, value: [1, 2, 3] }] 130 | }); 131 | var $ = cheerio.load(chart); 132 | expect($('.ct-legend').length).to.equal(0); 133 | })); 134 | it('variable excluded if nameless', () => co(function * () { 135 | var name = 'Y Axis (units)'; 136 | var chart = yield generate('bar', {}, { 137 | labels: ['a', 'b', 'c'], 138 | series: [ 139 | { name, value: [1, 2, 3] }, 140 | [1, 2, 3], 141 | { name, value: [1, 2, 3] } 142 | ] 143 | }); 144 | var $ = cheerio.load(chart); 145 | expect($('.ct-legend .ct-variable').length).to.equal(2); 146 | })); 147 | }); 148 | }); 149 | 150 | describe('rendered output', () => co(function * () { 151 | try { 152 | var options = { 153 | width: 400, 154 | height: 200, 155 | axisX: { 156 | // position: 'start', 157 | title: 'X Axis (units)', 158 | offset: 40 159 | }, 160 | axisY: { 161 | // position: 'end', 162 | title: 'Y Axis (units)', 163 | offset: 50 164 | } 165 | }; 166 | var data = { 167 | labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Friday'], 168 | series: [ 169 | {name: 'Test', value: [50, 20, 40, 20, 10]}, 170 | [4,1,2,6,7], 171 | {name: 'Test 3', value: [20, 60, 10, 80, 40]} 172 | ] 173 | }; 174 | 175 | console.time('bar'); 176 | var bar = yield generate('bar', options, data); 177 | console.timeEnd('bar'); 178 | 179 | console.time('line'); 180 | var line = yield generate('line', options, data); 181 | console.timeEnd('line'); 182 | 183 | console.time('pie'); 184 | var pieOptions = {width: 200, height: 200}; 185 | var pieData = {series: [{name: 'Test 1', value: 50}, 10, {name: 'Test 3', value: 20}]}; 186 | var pie = yield generate('pie', pieOptions, pieData); 187 | console.timeEnd('pie'); 188 | 189 | // Load external deps 190 | var css = fs.readFileSync(path.resolve(__dirname, '../dist/main.css'), 'utf8'); 191 | var style = ``; 192 | console.log(`${style}${bar}${line}${pie}`); 193 | } 194 | catch (error) { console.error(error.stack); } 195 | })); 196 | --------------------------------------------------------------------------------