├── .drone.yml
├── .gitignore
├── .jscsrc
├── .jshintrc
├── .npmignore
├── .travis.yml
├── Dockerfile
├── Gruntfile.js
├── LICENSE
├── README.md
├── img
├── .DS_Store
└── chartjsnode.png
├── index.js
├── package.json
└── test
└── index.js
/.drone.yml:
--------------------------------------------------------------------------------
1 | build:
2 | image: sedouard/node-mongo:latest
3 | commands:
4 | - npm set registry http://10.0.0.5:4873/
5 | - npm install
6 | environment:
7 | cache:
8 | mount:
9 | - node_modules
10 | - .git
11 | publish:
12 | npm:
13 | username: vmpowerci
14 | password: VMPOWER
15 | email: support@vmpower.io
16 | registry: "http://10.0.0.5:4873"
17 | always_auth: true
18 | when:
19 | branch: master
20 |
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | .vscode/
4 |
--------------------------------------------------------------------------------
/.jscsrc:
--------------------------------------------------------------------------------
1 | {
2 | "disallowEmptyBlocks": true,
3 | "disallowKeywords": [
4 | "with"
5 | ],
6 | "disallowKeywordsOnNewLine": [
7 | "else"
8 | ],
9 | "disallowMixedSpacesAndTabs": true,
10 | "disallowMultipleLineBreaks": true,
11 | "disallowNewlineBeforeBlockStatements": true,
12 | "disallowPaddingNewlinesInBlocks": false,
13 | "disallowQuotedKeysInObjects": true,
14 | "disallowSpaceAfterObjectKeys": true,
15 | "disallowSpaceAfterPrefixUnaryOperators": true,
16 | "disallowSpaceBeforeBinaryOperators": [
17 | ],
18 | "disallowSpaceBeforePostfixUnaryOperators": true,
19 | "disallowSpacesInsideArrayBrackets": true,
20 | "disallowSpacesInsideObjectBrackets": "all",
21 | "disallowSpacesInsideParentheses": true,
22 | "disallowTrailingComma": true,
23 | "disallowTrailingWhitespace": true,
24 | "disallowYodaConditions": true,
25 | "requireBlocksOnNewline": 1,
26 | "requireCamelCaseOrUpperCaseIdentifiers": "ignoreProperties",
27 | "requireCapitalizedConstructors": true,
28 | "requireCommaBeforeLineBreak": true,
29 | "requireCurlyBraces": [
30 | "if",
31 | "else",
32 | "for",
33 | "while",
34 | "do",
35 | "try",
36 | "catch"
37 | ],
38 | "requireDotNotation": true,
39 | "requireLineFeedAtFileEnd": true,
40 | "requireParenthesesAroundIIFE": true,
41 | "requireSpaceAfterBinaryOperators": true,
42 | "requireSpaceAfterKeywords": [
43 | "else",
44 | "for",
45 | "while",
46 | "do",
47 | "switch",
48 | "case",
49 | "return",
50 | "try",
51 | "function",
52 | "typeof"
53 | ],
54 | "requireSpaceAfterLineComment": "allowSlash",
55 | "requireSpaceBeforeBinaryOperators": true,
56 | "requireSpaceBeforeBlockStatements": true,
57 | "requireSpacesInConditionalExpression": true,
58 | "safeContextKeyword": [
59 | "self"
60 | ],
61 | "validateIndentation": 4,
62 | "validateQuoteMarks": "'"
63 | }
64 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "esversion": 6,
3 | "node": true,
4 | "browser": true,
5 | "nomen": false,
6 | "bitwise": true,
7 | "eqeqeq": true,
8 | "forin": true,
9 | "immed": true,
10 | "latedef": true,
11 | "newcap": true,
12 | "noarg": true,
13 | "noempty": true,
14 | "nonew": true,
15 | "plusplus": true,
16 | "regexp": true,
17 | "undef": true,
18 | "unused": true,
19 | "trailing": true,
20 | "indent": 4,
21 | "onevar": true,
22 | "white": true,
23 | "quotmark": "single",
24 | "predef": {
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | img
2 |
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: required
2 |
3 | language: nodejs
4 |
5 | services:
6 | - docker
7 |
8 | before_install:
9 | - docker build -t sedouard/node-chartjs .
10 |
11 | script:
12 | - docker run sedouard/node-chartjs
13 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:5
2 | RUN apt-get update
3 | RUN apt-get install libcairo2-dev libpango1.0-dev libgif-dev libjpeg62-turbo-dev build-essential g++ -y
4 | # Fix bug https://github.com/npm/npm/issues/9863
5 | RUN cd $(npm root -g)/npm \
6 | && npm install fs-extra \
7 | && sed -i -e s/graceful-fs/fs-extra/ -e s/fs\.rename/fs.move/ ./lib/utils/rename.js
8 | RUN npm install -g grunt-cli mocha-cli
9 | RUN npm install chart.js@"<=2.6.*"
10 | # Output debug logs in test output
11 | ENV DEBUG=chartjs-node*
12 | # FILES FOR BUILD
13 | ADD ./test ./test
14 | ADD Gruntfile.js ./Gruntfile.js
15 | ADD ./package.json ./package.json
16 | ADD ./index.js ./index.js
17 | ADD ./.jshintrc ./.jshintrc
18 | ADD ./.jscsrc ./.jscsrc
19 | # END FILES FOR BUILD
20 | RUN npm install
21 | CMD grunt test
22 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | var grunt = require('grunt');
2 | require('load-grunt-tasks')(grunt);
3 |
4 | var files = ['test/**/*.js', 'index.js'];
5 |
6 | grunt.initConfig({
7 | mochacli: {
8 | options: {
9 | reporter: 'spec',
10 | bail: false
11 | },
12 | all: ['test/*.js']
13 | },
14 | jshint: {
15 | files: files,
16 | options: {
17 | jshintrc: '.jshintrc'
18 | }
19 | },
20 | jscs: {
21 | files: {
22 | src: files
23 | },
24 | options: {
25 | config: '.jscsrc',
26 | esnext: true
27 | }
28 | },
29 | jsbeautifier: {
30 | write: {
31 | files: {
32 | src: files
33 | },
34 | options: {
35 | config: './.beautifyrc'
36 | }
37 | }
38 | }
39 | });
40 | grunt.registerTask('test', ['mochacli', 'jshint', 'jscs']);
41 | grunt.registerTask('validate', ['jshint', 'jscs']);
42 | grunt.registerTask('beautify', ['jsbeautifier']);
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016 Steven Edouard
2 |
3 | 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:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | 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.
8 |
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](http://chartjs-demo.vmpower.io/)
2 | [](https://travis-ci.org/vmpowerio/chartjs-node)
3 | [](https://codeclimate.com/github/vmpowerio/chartjs-node)
4 |
5 | # Chartjs-Node
6 |
7 | A simple library to make it easy to create Chartjs charts in Node.js (server-side).
8 |
9 | This library puts together [jsdom](https://github.com/tmpvar/jsdom), [node-canvas](https://github.com/Automattic/node-canvas) and [chartjs](https://github.com/chartjs/Chart.js) to render Chartjs on the server.
10 |
11 | **[Live Demo](http://chartjs-demo.vmpower.io)**
12 |
13 | ## Getting Started
14 |
15 | ### Peer Dependencies
16 |
17 | You'll need to npm install `chart.js`. This library will pick up the exact version you end up installing.
18 |
19 | ### Cairo
20 |
21 | Before installing this library you'll need to install Cairo for your system. The instructions for the most common platforms can be found [here](https://github.com/Automattic/node-canvas#installation).
22 |
23 | Now you're ready to install the package:
24 |
25 | ```
26 | npm install chartjs-node
27 | ```
28 |
29 | ## Creating a Chart
30 |
31 | ```js
32 | const ChartjsNode = require('chartjs-node');
33 | // 600x600 canvas size
34 | var chartNode = new ChartjsNode(600, 600);
35 | return chartNode.drawChart(chartJsOptions)
36 | .then(() => {
37 | // chart is created
38 |
39 | // get image as png buffer
40 | return chartNode.getImageBuffer('image/png');
41 | })
42 | .then(buffer => {
43 | Array.isArray(buffer) // => true
44 | // as a stream
45 | return chartNode.getImageStream('image/png');
46 | })
47 | .then(streamResult => {
48 | // using the length property you can do things like
49 | // directly upload the image to s3 by using the
50 | // stream and length properties
51 | streamResult.stream // => Stream object
52 | streamResult.length // => Integer length of stream
53 | // write to a file
54 | return chartNode.writeImageToFile('image/png', './testimage.png');
55 | })
56 | .then(() => {
57 | // chart is now written to the file path
58 | // ./testimage.png
59 | });
60 | ```
61 |
62 | ## Destroying the Chart
63 |
64 | Each time you create a chart, you will create a new virtual browser `window`. You should call the `destroy`
65 | method to release the native resources or you may leak memory:
66 |
67 | ```
68 | chartNode.destroy();
69 | ```
70 |
71 | ## Global chart reference
72 |
73 | You can access and modify the ChartJS reference before a chart is drawn via an event (`beforeDraw`). ChartjsNode extends [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter).
74 |
75 | ```js
76 | var chartNode = new ChartjsNode(600, 600);
77 | chartNode.on('beforeDraw', function (Chartjs) {
78 | //Chartjs.defaults
79 | //Chartjs.pluginService
80 | //Chartjs.scaleService
81 | //Chartjs.layoutService
82 | //Chartjs.helpers
83 | //Chartjs.controllers
84 | //etc
85 | });
86 | chartNode.drawChart(chartJsOptions) //beforeDraw is called in here
87 | ...
88 | ```
89 |
90 | ## Adding draw plugins
91 |
92 | To use draw plugins, simply use the ``options`` object to add your plugins, like so:
93 | ```js
94 | var myChartOptions = {
95 | plugins: {
96 | afterDraw: function (chart, easing) {
97 | var self = chart.config; /* Configuration object containing type, data, options */
98 | var ctx = chart.chart.ctx; /* Canvas context used to draw with */
99 | ...
100 | }
101 | }
102 | }
103 |
104 | var chartJsOptions = {
105 | type: 'pie',
106 | data: myChartData,
107 | options: myChartOptions
108 | };
109 | ```
110 |
111 | [Read here](http://www.chartjs.org/docs/latest/developers/plugins.html) to see what plugins you can write. In the context of drawing static images, ``beforeDraw`` and/or ``afterDraw`` methods makes most sense to implement.
112 |
113 | [Read here](https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D) to see which methods are available for the ``ctx`` object.
114 |
115 | ## Adding custom charts
116 |
117 | To use custom charts, also use the ``options`` object to add your chart config and controller, like so:
118 | ```js
119 | var myChartOptions = {
120 | charts: [{
121 | type: 'custom',
122 | baseType: 'bar',
123 | controller: {
124 | draw: function (ease) {},
125 | ...
126 | },
127 | defaults: {
128 | ...
129 | },
130 | }]
131 | }
132 |
133 | var chartJsOptions = {
134 | type: 'custom',
135 | data: myChartData,
136 | options: myChartOptions
137 | };
138 | ```
139 |
140 | [Read here](http://www.chartjs.org/docs/latest/developers/charts.html) to see how to write custom charts.
141 |
--------------------------------------------------------------------------------
/img/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vmpowerio/chartjs-node/8c10855d4c613d860363ce60bd58d822e5846242/img/.DS_Store
--------------------------------------------------------------------------------
/img/chartjsnode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vmpowerio/chartjs-node/8c10855d4c613d860363ce60bd58d822e5846242/img/chartjsnode.png
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const BbPromise = require('bluebird');
3 | const jsdom = BbPromise.promisifyAll(require('jsdom'));
4 | const fs = BbPromise.promisifyAll(require('fs'));
5 | const streamBuffers = require('stream-buffers');
6 | const EventEmitter = require('events');
7 | jsdom.defaultDocumentFeatures = {
8 | FetchExternalResources: ['script'],
9 | ProcessExternalResources: true
10 | };
11 |
12 | class ChartjsNode extends EventEmitter {
13 | /**
14 | * Creates an instance of ChartjsNode.
15 | * @param {number} width The width of the chart canvas.
16 | * @param {number} height The height of the chart canvas.
17 | */
18 | constructor(width, height, devicePixelRatio) {
19 | super();
20 | this._width = width;
21 | this._height = height;
22 | this._devicePixelRatio = devicePixelRatio || 1;
23 | }
24 | /**
25 | * @returns {Number} the width of the chart/canvas in pixels
26 | */
27 | get width() {
28 | return this._width;
29 | }
30 | /**
31 | * @returns {Number} the height of the chart/canvas in pixels
32 | */
33 | get height() {
34 | return this._height;
35 | }
36 | _disableDynamicChartjsSettings(configuration) {
37 | configuration.options.responsive = false;
38 | configuration.options.animation = false;
39 | configuration.options.width = this.width;
40 | configuration.options.height = this.height;
41 | }
42 | /**
43 | * Draws the chart given the Chart.js configuration
44 | *
45 | * @returns {Promise} A promise that will resolve when the chart is completed
46 | */
47 | drawChart(configuration) {
48 | // ensure we clean up any existing window if drawChart was called more than once.
49 | this.destroy();
50 | return jsdom.envAsync(
51 | `
52 |
53 |
54 |
55 |
56 |
57 | `,[]).then(window => {
58 |
59 | window.devicePixelRatio = this._devicePixelRatio;
60 | this._window = window;
61 | const canvas = require('canvas');
62 | const canvasMethods = ['HTMLCanvasElement'];
63 |
64 | // adding window properties to global (only properties that are not already defined).
65 | this._windowPropertiesToDestroy = [];
66 | Object.keys(window).forEach(property => {
67 | if (typeof global[property] === 'undefined') {
68 | if (typeof global[property] === 'undefined') {
69 | global[property] = window[property];
70 | this._windowPropertiesToDestroy.push(property);
71 | }
72 | }
73 | });
74 |
75 | // adding all window.HTMLCanvasElement methods to global.HTMLCanvasElement
76 | canvasMethods.forEach(method =>
77 | global[method] = window[method]
78 | );
79 |
80 | global.CanvasRenderingContext2D = canvas.Context2d;
81 |
82 | global.navigator = {
83 | userAgent: 'node.js'
84 | };
85 |
86 | const Chartjs = require('chart.js');
87 | this.emit('beforeDraw', Chartjs);
88 | if (configuration.options.plugins) {
89 | Chartjs.pluginService.register(configuration.options.plugins);
90 | }
91 | if (configuration.options.charts) {
92 | configuration.options.charts.forEach(chart => {
93 | Chartjs.defaults[chart.type] = chart.defaults || {};
94 | if (chart.baseType) {
95 | Chartjs.controllers[chart.type] = Chartjs.controllers[chart.baseType].extend(chart.controller);
96 | } else {
97 | Chartjs.controllers[chart.type] = Chartjs.DatasetController.extend(chart.controller);
98 | }
99 | });
100 | }
101 |
102 | this._disableDynamicChartjsSettings(configuration);
103 | this._canvas = window.document.getElementById('myChart');
104 | this._ctx = this._canvas.getContext('2d');
105 |
106 | this._chart = new Chartjs(this._ctx, configuration);
107 | return this._chart;
108 | });
109 |
110 | }
111 | /**
112 | * Retrives the drawn chart as a stream
113 | *
114 | * @param {String} imageType The image type name. Valid values are image/png image/jpeg
115 | * @returns {Stream} The image as an in-memory stream
116 | */
117 | getImageStream(imageType) {
118 | return this.getImageBuffer(imageType)
119 | .then(buffer => {
120 | var readableStream = new streamBuffers.ReadableStreamBuffer({
121 | frequency: 10, // in milliseconds.
122 | chunkSize: 2048 // in bytes.
123 | });
124 | readableStream.put(buffer);
125 | readableStream.stop();
126 | return {
127 | stream: readableStream,
128 | length: buffer.length
129 | };
130 | });
131 | }
132 | /**
133 | * Retrives the drawn chart as a buffer
134 | *
135 | * @param {String} imageType The image type name. Valid values are image/png image/jpeg
136 | * @returns {Array} The image as an in-memory buffer
137 | */
138 | getImageBuffer(imageType) {
139 | return new BbPromise((resolve, reject) => {
140 | this._canvas.toBlob((blob, err) => {
141 | if (err) {
142 | return reject(err);
143 | }
144 | var buffer = jsdom.blobToBuffer(blob);
145 | return resolve(buffer);
146 | }, imageType);
147 | });
148 | }
149 | /**
150 | * Returns image in the form of Data Url
151 | *
152 | * @param {String} imageType The image type name. Valid values are image/png image/jpeg
153 | * @returns {Promise} A promise that resolves when the image is received in the form of data url
154 | */
155 | getImageDataUrl(imageType) {
156 | return new BbPromise((resolve, reject) => {
157 | this._canvas.toDataURL(imageType,(err, img) => {
158 | if (err) {
159 | return reject(err);
160 | }
161 | return resolve(img);
162 | });
163 | });
164 | }
165 | /**
166 | * Writes chart to a file
167 | *
168 | * @param {String} imageType The image type name. Valid values are image/png image/jpeg
169 | * @returns {Promise} A promise that resolves when the image is written to a file
170 | */
171 | writeImageToFile(imageType, filePath) {
172 | return this.getImageBuffer(imageType)
173 | .then(buffer => {
174 | return new BbPromise((resolve,reject) => {
175 | var out = fs.createWriteStream(filePath);
176 | out.on('finish', () => {
177 | return resolve();
178 | });
179 | out.on('error', err => {
180 | return reject(err);
181 | });
182 | out.write(buffer);
183 | out.end();
184 | });
185 | });
186 | }
187 | /**
188 | * Destroys the virtual DOM and canvas -- releasing any native resources
189 | */
190 | destroy() {
191 | if (this._chart) {
192 | this._chart.destroy();
193 | }
194 |
195 | if (this._windowPropertiesToDestroy) {
196 | this._windowPropertiesToDestroy.forEach((prop) => {
197 | delete global[prop];
198 | });
199 | }
200 |
201 | if (this._window) {
202 | this._window.close();
203 | delete this._window;
204 | }
205 |
206 | delete this._windowPropertiesToDestroy;
207 | delete global.navigator;
208 | delete global.CanvasRenderingContext2D;
209 | }
210 | }
211 | module.exports = ChartjsNode;
212 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chartjs-node",
3 | "version": "1.7.1",
4 | "description": "Create Chart.js chart images server-side",
5 | "main": "index.js",
6 | "directories": {
7 | "test": "test"
8 | },
9 | "scripts": {
10 | "test": "grunt test"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/vmpowerio/chartjs-node.git"
15 | },
16 | "keywords": [
17 | "Chartjs",
18 | "Charts",
19 | "Plots",
20 | "Graphs"
21 | ],
22 | "author": "sedouard",
23 | "license": "MIT",
24 | "bugs": {
25 | "url": "https://github.com/vmpowerio/chartjs-node/issues"
26 | },
27 | "homepage": "https://github.com/vmpowerio/chartjs-node#readme",
28 | "dependencies": {
29 | "bluebird": "^3.4.6",
30 | "canvas": "1.6.*",
31 | "debug": "^2.2.0",
32 | "jsdom": "9.8.*",
33 | "stream-buffers": "^3.0.1"
34 | },
35 | "peerDependencies": {
36 | "chart.js": "<=2.7.*"
37 | },
38 | "devDependencies": {
39 | "grunt": "^1.0.1",
40 | "grunt-contrib-jshint": "^1.0.0",
41 | "grunt-jscs": "^2.8.0",
42 | "grunt-mocha-cli": "^2.1.0",
43 | "load-grunt-tasks": "^3.5.2",
44 | "mocha": "^3.0.2"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | /* globals describe, it*/
2 | 'use strict';
3 | const debug = require('debug')('chartjs-node:test');
4 | const assert = require('assert');
5 | const ChartjsNode = require('../index.js');
6 | const fs = require('fs');
7 | const stream = require('stream');
8 | /**
9 | * Schedule compute test. Tests a variety of schedules to validate correctness
10 | */
11 | describe('chartjs', function () {
12 |
13 | function runScenarios(chartConfig) {
14 | describe('#destroy', function () {
15 | it('should destroy the in-memory window', function () {
16 | var chartNode = new ChartjsNode(600, 600);
17 | return chartNode.drawChart(chartConfig)
18 | .then(() => {
19 | chartNode.destroy();
20 | // check if there are window properties to destroy from node global object
21 | assert(!chartNode._windowPropertiesToDestroy);
22 | assert(!chartNode._window);
23 | debug('Sucessfully destroyed in-memory window properties');
24 | });
25 | });
26 | });
27 | describe('#drawChart', function () {
28 | it('should draw the chart to a file', function () {
29 | var chartNode = new ChartjsNode(600, 600);
30 | return chartNode.drawChart(chartConfig)
31 | .then(() => {
32 | assert.ok(chartNode);
33 | return chartNode.writeImageToFile('image/png', './testimage.png');
34 | })
35 | .then(() => {
36 | assert(fs.existsSync('./testimage.png'));
37 | // clean up
38 | fs.unlinkSync('./testimage.png');
39 | debug('Sucessfully wrote image to a file');
40 | });
41 | });
42 | it('should draw the chart to a buffer', function () {
43 | var chartNode = new ChartjsNode(600, 600);
44 | return chartNode.drawChart(chartConfig)
45 | .then(() => {
46 | assert.ok(chartNode);
47 | return chartNode.getImageBuffer('image/png');
48 | })
49 | .then(buffer => {
50 | assert(buffer.length > 1);
51 | assert(buffer instanceof Buffer);
52 | debug('Sucessfully wrote image to a Buffer');
53 | });
54 | });
55 | it('should draw the chart to a stream', function () {
56 | var chartNode = new ChartjsNode(600, 600);
57 | return chartNode.drawChart(chartConfig)
58 | .then(() => {
59 | assert.ok(chartNode);
60 | return chartNode.getImageStream('image/png');
61 | })
62 | .then(imageStream => {
63 | assert(imageStream.stream instanceof stream.Readable);
64 | var length = imageStream.length;
65 | var readLength = 0;
66 | return new Promise((resolve, reject) => {
67 | imageStream.stream.on('data', d => {
68 | readLength += d.length;
69 | if (readLength === length) {
70 | debug('Sucessfully wrote image to a Readable stream');
71 | resolve();
72 | }
73 | });
74 | setTimeout(() => {
75 | debug('length: ' + length);
76 | debug('readLength: ' + readLength);
77 | reject('Failed to read complete chart image stream in time');
78 | }, 1000);
79 | });
80 | });
81 | });
82 | it('should return the image as data url', function () {
83 | var chartNode = new ChartjsNode(600, 600);
84 | return chartNode.drawChart(chartConfig)
85 | .then(() => {
86 | assert.ok(chartNode);
87 | return chartNode.getImageDataUrl('image/png');
88 | })
89 | .then(imageData => {
90 | assert(imageData.length > 1);
91 | debug('Sucessfully wrote image to a Readable stream');
92 | });
93 | });
94 | });
95 | describe('beforeDraw event', function () {
96 | it('should provide a reference to global Chartjs instance', function () {
97 | var Chartjs = null;
98 | var chartNode = new ChartjsNode(600, 600);
99 | chartNode.on('beforeDraw', function (c) {
100 | Chartjs = c;
101 | });
102 | return chartNode.drawChart(chartConfig)
103 | .then(() => {
104 | assert.ok(Chartjs);
105 | chartNode.destroy();
106 | debug('Sucessfully emitted beforeDraw event');
107 | });
108 | });
109 | });
110 | }
111 |
112 | var testPlugin = {
113 | beforeDraw: function (chartController, easingValue, pluginOptions) {
114 | var ctx = chartController.chart.ctx;
115 | ctx.textAlign = 'center';
116 | ctx.textBaseline = 'middle';
117 | ctx.font = '10px Georgia';
118 | ctx.fillText(pluginOptions.text, 0, 0);
119 | return true;
120 | }
121 | };
122 | var testPluginOptions = {
123 | testPlugin: {
124 | text: 'Hello World'
125 | }
126 | };
127 | var testchart = {
128 | type: 'custom',
129 | controller: {
130 | addElements: function () {},
131 | addElementAndReset: function () {},
132 | buildOrUpdateElements: function () {},
133 | getMeta: function () {},
134 | transition: function () {},
135 | initialize: function (chart) {
136 | this._chart = chart;
137 | },
138 | draw: function () {
139 | var options = this._chart.config.options;
140 | var ctx = this._chart.ctx;
141 | ctx.textAlign = 'center';
142 | ctx.textBaseline = 'middle';
143 | ctx.font = options.customFont;
144 | ctx.fillText('Hello World!', 0, 0);
145 | return true;
146 | }
147 | },
148 | defaults: {
149 | customFont: '10px Georgia'
150 | }
151 | };
152 |
153 | describe('with no charts or plugins,', function () {
154 |
155 | var chartConfig = {
156 | type: 'bar',
157 | data: {
158 | labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
159 | datasets: [{
160 | label: '# of Votes',
161 | data: [12, 19, 3, 5, 2, 3],
162 | backgroundColor: [
163 | 'rgba(255, 99, 132, 0.2)',
164 | 'rgba(54, 162, 235, 0.2)',
165 | 'rgba(255, 206, 86, 0.2)',
166 | 'rgba(75, 192, 192, 0.2)',
167 | 'rgba(153, 102, 255, 0.2)',
168 | 'rgba(255, 159, 64, 0.2)'
169 | ],
170 | borderColor: [
171 | 'rgba(255,99,132,1)',
172 | 'rgba(54, 162, 235, 1)',
173 | 'rgba(255, 206, 86, 1)',
174 | 'rgba(75, 192, 192, 1)',
175 | 'rgba(153, 102, 255, 1)',
176 | 'rgba(255, 159, 64, 1)'
177 | ],
178 | borderWidth: 1
179 | }]
180 | },
181 | options: {
182 | responsive: false,
183 | width: 400,
184 | height: 400,
185 | animation: false,
186 | scales: {
187 | yAxes: [{
188 | ticks: {
189 | beginAtZero: true
190 | }
191 | }]
192 | },
193 | tooltips: {
194 | mode: 'label'
195 | }
196 | }
197 | };
198 | runScenarios(chartConfig);
199 | });
200 |
201 | describe('with no charts but plugins,', function () {
202 |
203 | var chartConfig = {
204 | type: 'bar',
205 | data: {
206 | labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
207 | datasets: [{
208 | label: '# of Votes',
209 | data: [12, 19, 3, 5, 2, 3],
210 | backgroundColor: [
211 | 'rgba(255, 99, 132, 0.2)',
212 | 'rgba(54, 162, 235, 0.2)',
213 | 'rgba(255, 206, 86, 0.2)',
214 | 'rgba(75, 192, 192, 0.2)',
215 | 'rgba(153, 102, 255, 0.2)',
216 | 'rgba(255, 159, 64, 0.2)'
217 | ],
218 | borderColor: [
219 | 'rgba(255,99,132,1)',
220 | 'rgba(54, 162, 235, 1)',
221 | 'rgba(255, 206, 86, 1)',
222 | 'rgba(75, 192, 192, 1)',
223 | 'rgba(153, 102, 255, 1)',
224 | 'rgba(255, 159, 64, 1)'
225 | ],
226 | borderWidth: 1
227 | }]
228 | },
229 | options: {
230 | responsive: false,
231 | width: 400,
232 | height: 400,
233 | animation: false,
234 | scales: {
235 | yAxes: [{
236 | ticks: {
237 | beginAtZero: true
238 | }
239 | }]
240 | },
241 | tooltips: {
242 | mode: 'label'
243 | },
244 | plugins: testPluginOptions
245 | },
246 | plugins: [testPlugin]
247 | };
248 | runScenarios(chartConfig);
249 | });
250 |
251 | describe('with charts but no plugins,', function () {
252 |
253 | describe('bar chart', function () {
254 |
255 | var chartConfig = {
256 | type: 'bar',
257 | data: {
258 | labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
259 | datasets: [{
260 | label: '# of Votes',
261 | data: [12, 19, 3, 5, 2, 3],
262 | backgroundColor: [
263 | 'rgba(255, 99, 132, 0.2)',
264 | 'rgba(54, 162, 235, 0.2)',
265 | 'rgba(255, 206, 86, 0.2)',
266 | 'rgba(75, 192, 192, 0.2)',
267 | 'rgba(153, 102, 255, 0.2)',
268 | 'rgba(255, 159, 64, 0.2)'
269 | ],
270 | borderColor: [
271 | 'rgba(255,99,132,1)',
272 | 'rgba(54, 162, 235, 1)',
273 | 'rgba(255, 206, 86, 1)',
274 | 'rgba(75, 192, 192, 1)',
275 | 'rgba(153, 102, 255, 1)',
276 | 'rgba(255, 159, 64, 1)'
277 | ],
278 | borderWidth: 1
279 | }]
280 | },
281 | options: {
282 | responsive: false,
283 | width: 400,
284 | height: 400,
285 | animation: false,
286 | scales: {
287 | yAxes: [{
288 | ticks: {
289 | beginAtZero: true
290 | }
291 | }]
292 | },
293 | tooltips: {
294 | mode: 'label'
295 | },
296 | charts: [testchart]
297 | }
298 | };
299 | runScenarios(chartConfig);
300 | });
301 |
302 | describe('custom chart', function () {
303 |
304 | var chartConfig = {
305 | type: 'custom',
306 | data: {
307 | datasets: [{
308 | label: '',
309 | data: []
310 | }]
311 | },
312 | options: {
313 | charts: [testchart]
314 | }
315 | };
316 | runScenarios(chartConfig);
317 | });
318 | });
319 |
320 | describe('with charts and plugins,', function () {
321 |
322 | describe('bar chart', function () {
323 |
324 | var chartConfig = {
325 | type: 'bar',
326 | data: {
327 | labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
328 | datasets: [{
329 | label: '# of Votes',
330 | data: [12, 19, 3, 5, 2, 3],
331 | backgroundColor: [
332 | 'rgba(255, 99, 132, 0.2)',
333 | 'rgba(54, 162, 235, 0.2)',
334 | 'rgba(255, 206, 86, 0.2)',
335 | 'rgba(75, 192, 192, 0.2)',
336 | 'rgba(153, 102, 255, 0.2)',
337 | 'rgba(255, 159, 64, 0.2)'
338 | ],
339 | borderColor: [
340 | 'rgba(255,99,132,1)',
341 | 'rgba(54, 162, 235, 1)',
342 | 'rgba(255, 206, 86, 1)',
343 | 'rgba(75, 192, 192, 1)',
344 | 'rgba(153, 102, 255, 1)',
345 | 'rgba(255, 159, 64, 1)'
346 | ],
347 | borderWidth: 1
348 | }]
349 | },
350 | options: {
351 | responsive: false,
352 | width: 400,
353 | height: 400,
354 | animation: false,
355 | scales: {
356 | yAxes: [{
357 | ticks: {
358 | beginAtZero: true
359 | }
360 | }]
361 | },
362 | tooltips: {
363 | mode: 'label'
364 | },
365 | charts: [testchart],
366 | plugins: testPluginOptions
367 | },
368 | plugins: [testPlugin]
369 | };
370 | runScenarios(chartConfig);
371 | });
372 |
373 | describe('custom chart', function () {
374 |
375 | var chartConfig = {
376 | type: 'custom',
377 | data: {
378 | datasets: [{
379 | label: '',
380 | data: []
381 | }]
382 | },
383 | options: {
384 | charts: [testchart],
385 | plugins: testPluginOptions
386 | },
387 | plugins: [testPlugin]
388 | };
389 | runScenarios(chartConfig);
390 | });
391 | });
392 | });
393 |
--------------------------------------------------------------------------------