/ChartjsNodeCanvas

A node renderer for Chart.js using canvas.

Primary LanguageTypeScriptMIT LicenseMIT

chartjs-node-canvas

GitHub codecov NPM packagephobia publish

A Node JS renderer for Chart.js using canvas.

Provides and alternative to chartjs-node that does not require jsdom (or the global variables that this requires) and allows chartJS as a peer dependency, so you can manage its version yourself.

Contents

  1. Installation
  2. Node JS version
  3. Features
  4. Limitations
  5. API
  6. Usage
  7. Full Example
  8. Known Issues

Installation

npm i chartjs-node-canvas chart.js

Node JS version

This is limited by the upstream dependency canvas.

See the package.json engines section for the current supported Node version. This is not limited using engineStrict so you can try a new version if you like. You will need to do a npm rebuild to rebuild the canvas binaries.

Features

  • Supports all Chart JS features and charts.
  • No heavy DOM virtualization libraries, thanks to a pull request to chart.js allowing it to run natively on node, requiring only a Canvas API.
  • Chart JS is a peer dependency, so you can bump and manage it yourself.
  • Provides a callback with the global ChartJS variable, so you can use the Global Configuration.
  • Uses (similar to) fresh-require for each instance of CanvasRenderService, so you can mutate the ChartJS global variable separately within each instance.
  • Support for custom fonts.

Limitations

Animations

Chart animation (and responsive resize) is disabled by this library. This is necessary since the animation API's required are not available in Node JS/canvas-node (this is not a browser environment after all).

This is the same as:

Chart.defaults.global.animation = false;
Chart.defaults.global.responsive = false;

SVG and PDF

You need to install ImageMagik.

For some unknown reason canvas requires use of the sync API's to use SVG's or PDF's. This libraries which support these are:

API

See the API docs.

Usage

const { CanvasRenderService } = require('chartjs-node-canvas');

const width = 400; //px
const height = 400; //px
const canvasRenderService = new CanvasRenderService(width, height, (ChartJS) => { });

(async () => {
    const configuration = {
        ... // See https://www.chartjs.org/docs/latest/configuration
    };
    const image = await canvasRenderService.renderToBuffer(configuration);
    const dataUrl = await canvasRenderService.renderToDataURL(configuration);
    const stream = canvasRenderService.renderToStream(configuration);
})();

Memory Management

Every instance of CanvasRenderService creates its own canvas. To ensure efficient memory and GC use make sure your implementation creates as few instances as possible and reuses them:

const { CanvasRenderService } = require('chartjs-node-canvas');

// Re-use one service, or as many as you need for different canvas size requirements
const smallCanvasRenderService = new CanvasRenderService(400, 400);
const bigCanvasRenderService = new CanvasRenderService(2000, 2000);

// Expose just the 'render' methods to downstream code so they don't have to worry about life-cycle management.
exports = {
    renderSmallChart: (configuration) => smallCanvasRenderService.renderToBuffer(configuration),
    renderBigChart: (configuration) => bigCanvasRenderService.renderToBuffer(configuration)
};

Custom Charts

Just use the ChartJS reference in the callback:

const canvasRenderService = new CanvasRenderService(width, height, (ChartJS) => {
    // New chart type example: https://www.chartjs.org/docs/latest/developers/charts.html
    ChartJS.controllers.MyType = Chart.DatasetController.extend({
        // chart implementation
    });
});

Global Config

Just use the ChartJS reference in the callback:

const canvasRenderService = new CanvasRenderService(width, height, (ChartJS) => {
    // Global config example: https://www.chartjs.org/docs/latest/configuration/
    ChartJS.defaults.global.elements.rectangle.borderWidth = 2;
});

Custom Fonts

Just use the registerFont method:

const canvasRenderService = new CanvasRenderService(width, height, (ChartJS) => {
    // Just example usage
    ChartJS.defaults.global.defaultFontFamily = 'VTKS UNAMOUR';
});
// Register before rendering any charts
canvasRenderService.registerFont('./testData/VTKS UNAMOUR.ttf', { family: 'VTKS UNAMOUR' });

See the node-canvas docs and the chart js docs.

Loading plugins

Newer plugins

Just use the ChartJS reference in the callback:

const canvasRenderService = new CanvasRenderService(width, height, (ChartJS) => {
    // Global plugin example: https://www.chartjs.org/docs/latest/developers/plugins.html
    ChartJS.plugins.register({
        // plugin implementation
    });
});

Older plugins

The key to getting older plugins working is knowing that this package uses an equivalent to fresh-require by default to retrieve its version of chart.js.

There are some tools you can use to solve any issues with the way older ChartJS plugins that do not use the newer global plugin registration API, and instead either load chartjs itself or expect a global variable:


  1. Temporary global variable for ChartJs:
const canvasRenderService = new CanvasRenderService(width, height, (ChartJS) => {
    global.Chart = ChartJS;
    require('<chart plugin>');
    delete global.Chart;
});

This should work for any plugin that expects a global Chart variable.


  1. Chart factory function for CanvasRenderService:
const chartJsFactory = () => {
    const chartJS = require('chart.js');
    require('<chart plugin>');
        // Clear the require cache so to allow `CanvasRenderService` separate instances of ChartJS and plugins.
    delete require.cache[require.resolve('chart.js')];
    delete require.cache[require.resolve('chart plugin')];
    return chartJS;
};
const canvasRenderService = new CanvasRenderService(width, height, undefined, undefined, chartJsFactory);

This will work for plugins that require ChartJS themselves.


  1. Register plugin directly with ChartJS:
const freshRequire = require('fresh-require');

const canvasRenderService = new CanvasRenderService(width, height, (ChartJS) => {
    // Use 'fresh-require' to allow `CanvasRenderService` separate instances of ChartJS and plugins.
    ChartJS.plugins.register(freshRequire('<chart plugin>', require));
});

This will work with plugins that just return a plugin object and do no specific loading themselves.


These approaches can be combined also.

Full Example

const { CanvasRenderService } = require('chartjs-node-canvas');

const width = 400;
const height = 400;
const chartCallback = (ChartJS) => {

    // Global config example: https://www.chartjs.org/docs/latest/configuration/
    ChartJS.defaults.global.elements.rectangle.borderWidth = 2;
    // Global plugin example: https://www.chartjs.org/docs/latest/developers/plugins.html
    ChartJS.plugins.register({
        // plugin implementation
    });
    // New chart type example: https://www.chartjs.org/docs/latest/developers/charts.html
    ChartJS.controllers.MyType = ChartJS.DatasetController.extend({
        // chart implementation
    });
};
const canvasRenderService = new CanvasRenderService(width, height, chartCallback);

(async () => {
    const configuration = {
        type: 'bar',
        data: {
            labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
            datasets: [{
                label: '# of Votes',
                data: [12, 19, 3, 5, 2, 3],
                backgroundColor: [
                    'rgba(255, 99, 132, 0.2)',
                    'rgba(54, 162, 235, 0.2)',
                    'rgba(255, 206, 86, 0.2)',
                    'rgba(75, 192, 192, 0.2)',
                    'rgba(153, 102, 255, 0.2)',
                    'rgba(255, 159, 64, 0.2)'
                ],
                borderColor: [
                    'rgba(255,99,132,1)',
                    'rgba(54, 162, 235, 1)',
                    'rgba(255, 206, 86, 1)',
                    'rgba(75, 192, 192, 1)',
                    'rgba(153, 102, 255, 1)',
                    'rgba(255, 159, 64, 1)'
                ],
                borderWidth: 1
            }]
        },
        options: {
            scales: {
                yAxes: [{
                    ticks: {
                        beginAtZero: true,
                        callback: (value) => '$' + value
                    }
                }]
            }
        }
    };
    const image = await canvasRenderService.renderToBuffer(configuration);
    const dataUrl = await canvasRenderService.renderToDataURL(configuration);
    const stream = canvasRenderService.renderToStream(configuration);
})();

Known Issues

There is a problem with persisting config objects between render calls, see this issue for details and workarounds.