ChartjsNodeCanvas icon indicating copy to clipboard operation
ChartjsNodeCanvas copied to clipboard

Any way to use the animation API?

Open el opened this issue 2 years ago • 8 comments

Hi, I understand that this package does not support animations. However, I am trying to achieve animations on node-canvas and I was wondering if you can provide a workaround or point me in the right direction. What I had on my mind was to add a plugin for afterDraw event (https://codepen.io/elizzk/pen/GRvaQBz?editors=0010) and render each frame into a picture to be used for a video generation later.

I think it is technically possible https://github.com/pankod/canvas2video (It uses fabric.js, and node-canvas)

el avatar Apr 25 '22 07:04 el

Hi there,

Interesting update, I was not aware that node-canvas now supports animations. I had a quick search and I think you are right in looking to use the afterDraw event to capture the individual frames.

I would think the best approach would be to store the frame buffers in memory until the chart animation is complete then render them to the file system. This way we can check for long-running animations and infinite loops etc. But writing to the file system asynchronously in the loop may also work.

After generating the individual frame images we can do something like this to create gif or videos from the set of frames.

While it is not a common use case I would be interested in adding support for this in this library, so would like to hear if you manage to get this working!

SeanSobey avatar May 03 '22 08:05 SeanSobey

Hi, sorry it took some time to get back to you. I had covid (one of the last people on earth I guess), but I am better now.

Yeah, I had the same idea. Currently, chartjs-node-canvas sets animation: false internally, I will try to make it work by updating your library. If it works as expected, maybe we can add this feature to the library to be used by others. I will let you know about the process.

el avatar May 24 '22 08:05 el

Covid is still fairly rampant in my country (South Africa) and many people I know have come down with the new variant recently, which is particularly unpleasant apparently. My father has managed to contract 4 of the 5 variants over the pandemics timeline, tough as nails that man. Good to hear you have recovered.

Here is an alternative to modifying this lib using OOP (the JS version at least) overrides to re-enable animations, might be useful to test:

const { ChartJSNodeCanvas } = require('./index');
const fs = require('fs');

class CustomChartJSNodeCanvas extends ChartJSNodeCanvas {
	renderChart(configuration) {
        const canvas = this._createCanvas(this._width, this._height, this._type);
        canvas.style = canvas.style || {};
        configuration.options = configuration.options || {};
        configuration.options.responsive = false;
        configuration.options.animation = true;
        const context = canvas.getContext('2d');
        global.Image = this._image; // Some plugins use this API
        const chart = new this._chartJs(context, configuration);
        delete global.Image;
        return chart;
    }
}

async function main() {

	const width = 400;
	const height = 400;
	const configuration = {
		...
		}
	};

	const chartJSNodeCanvas = new CustomChartJSNodeCanvas({ width, height });
	const buffer = await chartJSNodeCanvas.renderToBuffer(configuration);
	await fs.writeFile('./test.png', buffer, 'base64');
}

main();

You will have to implement/mock any browser APIs used by chartjs that nodejs does not provide, eg:

	...
	const chartJSNodeCanvas = new CustomChartJSNodeCanvas({ width, height });
	global.window = {};
	window.requestAnimationFrame = setImmediate; // Looks like the only one used, but not sure this works or if there are others
	const buffer = await chartJSNodeCanvas.renderToBuffer(configuration);
	...

As mentioned the heavy lifting will be in the collating of the image buffers and rendering them into something useful (gif/video/etc). I have no experience with this but I imagine putting the chartJSNodeCanvas.renderToBuffer in a loop (emulating the fps), collecting the results and using another lib or something to generate the required result.

Good luck and interested in any findings!

SeanSobey avatar May 24 '22 23:05 SeanSobey

Thanks a lot for the nice words. The covid has come down a lot (below 1000 cases) in my country, Turkey. But, I guess I had to get it after 2 years :)

I have experience working with images and videos, so as long as I generate multiple frame buffers, I can convert them to a video easily using fluent-ffmpeg. I made the same changes you provided yesterday, but I couldn't make it run an animation yet. the afterDraw event is only called once for some reason. I may need to change the requestAnimationFrame function with a custom one to wait and call only 60 times per second (or the framerate that is passed)

el avatar May 25 '22 09:05 el

Maybe the animation callbacks are a better option? They look to provide timing information also.

From what I know requestAnimationFrame is synced to monitor refresh rate and guaranteed to be called once per frame to prevent things like shear, flicker, and frame skipping. We will have to watch for that using a *timeout equivalent on node but I have no reason to expect them. A quick search found this polyfill, maybe it is more robust.

SeanSobey avatar May 25 '22 17:05 SeanSobey

I made it all work except the requestAnimationFrame part, I am unable to modify the window object so that it is used on chartjs. And since chartjs has a workaround that immediately calls the callback, it does not wait between frames as it should. as a result, it produces an image in between: image

Here is the code if you would like to play around: https://github.com/el/ChartjsNodeCanvas

el avatar May 26 '22 12:05 el

Unfortunately, that polyfill did not work correctly since it uses 16ms between frames instead of 1000/60. I was able to generate a given number of frames successfully. You can check the code at https://github.com/el/ChartjsNodeCanvas

Some frames still have issues related to drawing, but overall I think it works :)

Here is the final result: https://codepen.io/elizzk/pen/vYdpEEL?editors=1010

el avatar May 26 '22 15:05 el

I have successfully generated frame images ☺️

https://codepen.io/elizzk/full/vYdpEEL

Would you like me to create a pr so that it can be part of this package?

el avatar May 27 '22 11:05 el