Add a second Y-axis for Linechart in Chart.js?

Sabba picture Sabba · Jun 24, 2015 · Viewed 10.8k times · Source

I would like to add a second Y-axis on a linechart, possibly on the right side of the canvas. I tried to use Chart.js taken from https://github.com/Wikunia/Chart.js/tree/Double-Y-Axis in which LineDoubleY is defined. But:

  1. I cannot see the example on Firefox:

ReferenceError: xPos is not defined Chart.js:1147:7

  1. If I use this Chart.js in my app:

currentChart.addData is not a function

This said: Is it possible to add a second Y-axis in another way?

Answer

potatopeelings picture potatopeelings · Jun 25, 2015

Here is a revised version of the original with a little more flexibility. The logic is pretty much the same, but extended to more than 2 datasets


Preview

enter image description here


Script

Chart.types.Line.extend({
    name: "Line2Y",
    getScale: function(data) {
        var startPoint = this.options.scaleFontSize;
        var endPoint = this.chart.height - (this.options.scaleFontSize * 1.5) - 5;
        return Chart.helpers.calculateScaleRange(
            data,
            endPoint - startPoint,
            this.options.scaleFontSize,
            this.options.scaleBeginAtZero,
            this.options.scaleIntegersOnly);
    },
    initialize: function (data) {
        var y2datasetLabels = [];
        var y2data = [];
        var y1data = [];
        data.datasets.forEach(function (dataset, i) {
            if (dataset.y2axis == true) {
                y2datasetLabels.push(dataset.label);
                y2data = y2data.concat(dataset.data);
            } else {
                y1data = y1data.concat(dataset.data);
            }
        });

        // use the helper function to get the scale for both datasets
        var y1Scale = this.getScale(y1data);
        this.y2Scale = this.getScale(y2data);
        var normalizingFactor = y1Scale.max / this.y2Scale.max;

        // update y2 datasets
        data.datasets.forEach(function(dataset) {
            if (y2datasetLabels.indexOf(dataset.label) !== -1) {
                dataset.data.forEach(function (e, j) {
                    dataset.data[j] = e * normalizingFactor;
                })
            }
        })

        // denormalize tooltip for y2 datasets
        this.options.multiTooltipTemplate = function (d) {
            if (y2datasetLabels.indexOf(d.datasetLabel) !== -1) 
                return Math.round(d.value / normalizingFactor, 6);
            else 
                return d.value;
        }

        Chart.types.Line.prototype.initialize.apply(this, arguments);
    },
    draw: function () {
        this.scale.xScalePaddingRight = this.scale.xScalePaddingLeft;
        Chart.types.Line.prototype.draw.apply(this, arguments);

        this.chart.ctx.textAlign = 'left';
        this.chart.ctx.textBaseline = "middle";
        this.chart.ctx.fillStyle = "#666";
        var yStep = (this.scale.endPoint - this.scale.startPoint) / this.y2Scale.steps
        for (var i = 0, y = this.scale.endPoint, label = this.y2Scale.min; 
             i <= this.y2Scale.steps; 
             i++) {
                this.chart.ctx.fillText(label, this.chart.width - this.scale.xScalePaddingRight + 10, y);
                y -= yStep;
                label += this.y2Scale.stepValue
        }
    }
});

You send a dataset to the y2 axis with an additional property (y2axis: true). For example

{
    label: "My Second dataset",
    fillColor: "rgba(151,187,205,0.5)",
    strokeColor: "rgba(151,187,205,1)",
    pointColor: "rgba(151,187,205,1)",
    pointStrokeColor: "#fff",
    data: [150, 48, 120, 19, 46, 27, 100],
    y2axis: true
}

Fiddle - http://jsfiddle.net/1va2kx18/


You could use one shade of colors for the series on the y axes and another for colors on the y2 axes (otherwise it's a bit confusing). Additionally you could modify your tooltip function to show the y2 values a bit differently. For example

return '[' + Math.round(d.value / normalizingFactor, 6) + ']';

would put square brackets around y2 values in the tooltip


If you are adding new points to the datasets using addData, there is an issue with dataset labels not being updated in the newly added points that you have to work around by updating the addData function.

If you don't want to do that just use the dataset point colors (instead of using dataset labels) to distinguish between the y and y2 series IF you use distinct point colors for y and y2 series. Here are the lines to substitute in

 var y2datasetColors = [];
 ...
 y2datasetColors.push(dataset.pointColor);
 ...
 if (y2datasetColors.indexOf(dataset.pointColor) !== -1) {
 ...
 if (y2datasetColors.indexOf(d._saved.fillColor) !== -1) 

where you previously had y2datasets