NodeJS
Prerequisites
- Copy the i18n folder and the chartbreaker.js file from the distribution bundle to your NodeJS project
- Add the
canvas
module (~2.6.1) to your dependency list in package.json
Configuring Charts
To use charts in your server, you can require
the chartbreaker.js file just like any other module.
The API is exactly the same as in the browser, with the exception of a few small differences:
- Redraws must be manually requested.
- The canvas cannot be resized after the first rendering pass
- Different output formats may be selected by passing a "_backend" object to the Controller's constructor
- The value of the "id" option passed to the Controller's constructor is completely arbitrary
- i18n data is directly loaded from files via
require
Output Formats
Currently, the library supports 3 different formats: PNG, SVG, and PDF. Both SVG and PDF generate true 100% scalable vector images. By default, Chartbreaker will generate PNG images.
const chart = new charting.Controller({
id: 'chart',
_backend: { // optional
format: 'pdf'
}
});
i18n Data
Similar to in the browser, the language data must be made available to Chartbreaker by using the BG.charts.setLanguage
method.
charting.setLanguageData('de', require('charting/core/de.json'));
Manually Updating Charts
By default, the contents of a chart are updated automatically by the library in the next tick following any changes. In a server environment, it can make sense to disable this behaviour by calling:
controller.update(false);
To manually trigger an update afterwards, you can then call:
controller.update();
Rendering Charts
In the browser, the chart is automatically rendered to the canvas when an update happens, and you can not manually redraw it.
In a NodeJS environment, however, the ChartSet object exposes an additional method render
.
Whenever you want to render the chart, you will have to manually call this method.
The Image
data can be accessed via the Canvas object exposed by ChartSet::getCanvas
, either as a
Buffer
, or as a Stream
.
Once a Controller is no longer needed, make sure to call destroy
; this will release the rendering context, unsubscribe from push data, etc.
Streams
To obtain a stream, you can use the createPNGStream
method on the Canvas object.
Asynchronous data access via Streams is only available when rendering PNGs; for PDF or SVG you have to use the synchronous Buffers.
const out = fs.createWriteStream('chart.png');
const data = controller.getChartSet().getCanvas().createPNGStream();
data.pipe(out);
data.once('end', function() {
// destroy the chart once rendering is finished
controller.destroy();
// wait until data is flushed to file then exit
out.once('finish', function(){
process.exit(0);
});
});
Buffers
const data = controller.getChartSet().getCanvas().toBuffer();
fs.writeFileSync("chart.png", data);
controller.destroy();
Full Example
const charting = require('./charting/core/');
const fs = require('fs');
// use the included random loader plugin
charting.setClazz(require('charting/loaders/Random'));
charting.setLanguageData('de', require('./charting/core/de.json'));
charting.setLanguageData('de', require('./charting/loaders/Random/de.json'));
// create a new controller
const chart = new charting.Controller({
id: 'chart',
_backend: {
format: 'image' // 'pdf' and 'svg' are also possible
}
});
// disable automatic updating - chart will not update content until update() is called manually
chart.update(false);
// add a timeseries using the previously registered loader
chart.addTimeSeries("Random()");
/*
* subscribe to `loaded` event which is triggered once all data has been loaded and chart is "ready"
*/
chart.once("loaded", function() {
const chartSet = chart.getChartSet();
/*
* rendering needs to be requested manually in the node.js environment
*/
chartSet.render();
/*
* when using the image backend, `createPNGStream` will provide a stream object
* alternatively, you can use `toBuffer` (only option for pdf and svg)
*/
const out = fs.createWriteStream('chart.png');
const data = chartSet.getCanvas().createPNGStream();
data.pipe(out);
data.once('end', function() {
// destroy the chart once rendering is finished
chartSet.destroy();
// wait until data is flushed to file then exit
out.once('finish', function(){
process.exit(0);
});
});
});
// update chart
chart.update();
Error Handling
Because the library uses asynchronous data loading, you can not handle all errors with a simple try/catch block. Errors could occur in various places and at arbitrary times - e.g. in a custom loader after an ajax request. However, almost all of these places can be traced back to a callback listening for an event on some object. The custom EventEmitter implementation used in the library therefore allows you to provide your own error handler which will receive all errors occuring in any listener callback.
const events = require('bg-events');
events.EventEmitter.guard(function(error) {
console.log("An error occured:", error);
});
obj.on("event", function() {
throw "x"; // will be forwarded to function above
});
Changing the error handler has no effect on previously registered listeners. They will still forward errors to the handler that was set when they were first registered.
events.EventEmitter.guard(function handlerA(error) {
console.log("Error handled by A");
});
object.on("eventA", function() {
throw "x";
});
events.EventEmitter.guard(function handlerB(error) {
console.log("Error handled by B");
});
obj.on("eventB", function() {
throw "x";
});
obj.emit("eventB"); // prints "Error handled by B"
obj.emit("eventA"); // prints "Error handled by A"
Depending on the type of your server, you might also require some context (e.g. an http request) to process an error. This can be achieved by creating individual error handlers for each context. All functions calling any library methods should then always reapply the correct error handler exactly once at the beginning, before using Chartbreaker.
For example, a server that calls processRequest
for every http request could implement the following:
function processRequest(req) {
let failed = false;
function guard(request) {
events.EventEmitter.guard(function(error) {
failed = true;
request.writeHead(500);
request.end("An error occured");
});
}
// apply error handling before creating chart
guard(req);
const controller = new charting.Controller(...);
// ... configure chart
controller.on("example", function() {
// another request might have started in the meantime
// => we need to reapply the correct context
// if we intend to do any additional asynchronous operations
guard(req);
// ... do something else
});
controller.on("loaded", function() {
if(failed) return; // do nothing if we already responded with an error code
controller.getChartSet().render();
const data = chartSet.getCanvas().createPNGStream();
data.pipe(req);
data.once('end', function() {
// destroy the chart once rendering is finished
chartSet.destroy();
});
})
};