Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Chartjs animate x-axis

I want to use a chartjs linechart to visualize my data points. Chartjs seems to animate the graph by default, but it does not animate the values on the x-axis. The x-axis only move in discrete steps.

Is there any way to enable animation on the axis also?

Thanks!

like image 957
Joakim Ericsson Avatar asked Jul 03 '16 17:07

Joakim Ericsson


People also ask

How to turn off animation chart js?

Disabling animation To disable an animation configuration, the animation node must be set to false , with the exception for animation modes which can be disabled by setting the duration to 0 . Copied!


1 Answers

As far as I am aware, ChartJS does not support x-axis animation out-of-the-box. So you'll have to hack it. There are several ways to possibly do this, but the following methods seems to work.

If You Want to Animate the Data On the X-Axis

When a chart is updated, the following steps occur: 1) The axes are drawn, and then 2) a draw() function is called to draw the data. There are different draw() functions for different chart types, and the function for line charts is Chart.controllers.line.prototype.draw. The draw() functions take one argument, which I will call animationFraction, that indicates how complete the animation is as a fraction. For instance, if an animation is 5% complete, animationFraction will be 0.05, and if an animation is 100% complete (i.e. if the chart is in its final form), animationFraction=1. The draw() function is called at each step of the animation to update the data display.

One hack to animate the x-axis then is to monkey-patch the line chart draw() function to translate the canvas in the horizontal dimension at every draw step:

var hShift = (1-animationFraction)*ctx.canvas.width;

hShift is the horizontal shift in pixels of the chart. As defined above, the data will sweep in from the right; if you want it to sweep in from the left, you can make the above negative. You then save the canvas context state, transform the canvas using hShift, draw the chart data, and then restore the canvas to its original state so that on the next animation frame the axes will be drawn in the correct spot:

ctx.save();
ctx.setTransform(1, 0, 0, 1, hShift, 0);
ctx.oldDraw.call(this, animationFraction);
ctx.restore();

In the above, this refers to the chart object, and oldDraw refers to the original line chart drawing function that was saved previously:

var oldDraw = Chart.controllers.line.prototype.draw;

You can additionally setup your new draw() function to read new animation options that allow you to set whether the x-axis and y-axis are animated:

var oldDraw = Chart.controllers.line.prototype.draw;
Chart.controllers.line.prototype.draw = function(animationFraction) {
    var animationConfig = this.chart.options.animation;
    if (animationConfig.xAxis === true) {
        var ctx = this.chart.chart.ctx;
        var hShift = (1-animationFraction)*ctx.canvas.width;
        ctx.save();
        ctx.setTransform(1, 0, 0, 1, hShift,0);
        if (animationConfig.yAxis === true) {
            oldDraw.call(this, animationFraction);
        } else {
            oldDraw.call(this, 1);
        }
        ctx.restore();
    } else if (animationConfig.yAxis === true) {
        oldDraw.call(this, animationFraction);
    } else {
        oldDraw.call(this, 1);
    }
}

You can then create a line chart with both axes animated with:

var lineChart = new Chart(ctx, {
    type: 'line',
    data: data,
    options: { 
        animation: { 
            duration: 5000,
            xAxis: true,
            yAxis: true,
        }
    }
});

See https://jsfiddle.net/16L8sk2p/ for a demo.

If You Want to Animate the X-Axis Limits

If you want to animate the x-axis limits--i.e. move the data, axis ticks, and tick labels, then you can use the following strategy. It's a bit quirky, so it might take some effort to work out the kinks for any given use-case, but I believe it should work generally. First, you'll need to convert the line plot to a scatter plot. Line charts have categorical x-axes that move in steps, so you can't set the axis limits to be between ticks, which is what you'll need to do to get the animation. So you'll need to use a line scatter plot instead, since scatter plots can have arbitrary axis limits. You can do this by numbering each data point, and assigning that number to the x-value for that data point. For instance, to generate a random dataset, you could do:

var DATA_POINT_NUM = 58;
var data = {
    labels: [],
    datasets: [
        {
            data: [],
        },
    ]
}
for (var i=0; i<DATA_POINT_NUM; i++) {
    data.datasets[0].data.push({    x: i,
                                    y: Math.random()*10
                                });
    data.labels.push(String.fromCharCode(65+i));
}

You'll then need to write a function to convert between the assigned x-values of your data points, and the data point labels (i.e. the categories that will be on the charts x-axis):

function getXAxisLabel(value) {
    try {
        var xMin = lineChart.options.scales.xAxes[0].ticks.min;
    } catch(e) {
        var xMin = undefined;
    }
    if (xMin === value) {
        return '';
    } else {
        return data.labels[value];
    }
}

where lineChart is our Chart object, which will be defined below. Note that ChartJS draws the chart slightly differently if there's a label at x-axis's minimum value, so you'll need to write this function to return an empty string if the value==the minimum value of the x-axis. You can then define the Chart object:

var lineChart = new Chart(ctx, {
    type: 'line',
    data: data,
    options: { 
        animation: false,
        scales: {
            xAxes: [{
                type: 'linear',
                position: 'bottom',
                ticks: {
                    min: 0,
                    max: 10,
                    callback: getXAxisLabel, // function(value) { return data.labels[value]; },
                    autoSkip: false,
                    maxRotation: 0,

                },
            }]
        }
    }
});

ticks.callback is set to our getXAxisLabel function above. When ChartJS draws the x-axis, it will pass the x-values of the data points to the callback function and then use the resulting string as the value on the x-axis. In this way, we can draw a scatter chart like a line chart. I've also set autoSkip=false and maxRotation=0 to make sure the axis labels get drawn in a consistent way.

You can then animate the chart by adjusting the x-axis ticks.min and ticks.max values and calling the chart's .update() method. To illustrate this, the code below scans along the charts x-axis, showing ten data points at a time.

var xMin = 0;                   // Starting minimum value for the x-axis
var xLength = 10;               // Length of the x-axis
var animationDuration = 5000;   // Duration of animation in ms

// Calculate animation properties
var framesPerSec = 100;
var frameTime = 1000/framesPerSec;
var xStep = (DATA_POINT_NUM-xMin+xLength)/(animationDuration/1000*framesPerSec);

function nextFrame() {
    var xMax = xMin+xLength;
    if (xMax < DATA_POINT_NUM-1) {

        if (xMax+xStep > DATA_POINT_NUM-1) {
            xMax = DATA_POINT_NUM-1;
            xMin = xMax-xLength;
        }
        lineChart.options.scales.xAxes[0].ticks.min = xMin;
        lineChart.options.scales.xAxes[0].ticks.max = xMax;
        lineChart.update();
        setTimeout(nextFrame, frameTime);
        xMin += 0.1;
    }
}

nextFrame();

Putting it all together: https://jsfiddle.net/qLhojncy/

like image 94
cjg Avatar answered Oct 16 '22 03:10

cjg