Chartjs-How can I add functionality to Chartjs Doughnut chart custom legend

0👍

here is the solution i was able to find, works for doughnut charts with n datasets.

   $('#' + chart_name_donut + '_dataLabels').on('change', function () {
    var selected = $('#' + chart_name_donut + '_dataLabels').val()
    if (selected == 'perc') {
        isValue = false;
    }
    else {
        isValue = true;
    }
    chart.update();
});

//data recieved from backend
inputarray = <%SERIES_LIST%>;
myData = getColor(inputarray);      //assign background color for each part

var myDoughnutExtend = Chart.controllers.doughnut.prototype.draw;

var ctxDoughnut = document.getElementById(chart_name_donut + "_Chart").getContext('2d');

//config options for the doughnut chart, 
var config = {
    // The type of chart we want to create
    type: 'doughnut',

    // The data for our dataset
    data: myData,

    // Configuration options go here
    options: {
        responsive: true,
        maintainAspectRatio: false,
        legendCallback: function (chart) {
            var text = [];
            var legsOuter = [];
            var legsInner = [];

            var innerLabel = "";
            var outerLabel = "";
            //custom legend
            for (var j = 0; j < chart.data.datasets.length; j++) {
                var legendLabel = chart.data.datasets[j].label;
                for (var i = 0; i < chart.data.datasets[j].data.length; i++) {
                    var newd = { label: chart.data.datasets[j].labels[i], color: chart.data.datasets[j].backgroundColor[i] };

                    if (j == 0) {
                        if (!containsObject(newd, legsOuter)) {
                            legsOuter.push(newd);
                            outerLabel = legendLabel;
                            outerkeyCount = outerkeyCount + 1;
                        }
                    }
                    else if (j == 1) {
                        if (!containsObject(newd, legsInner)) {
                            legsInner.push(newd);
                            innerLabel = legendLabel;
                            innerkeyCount = innerkeyCount + 1;
                        }
                    }
                }
            }

            //change text-align from center to start(left) and end(right)
            text.push('<ul class="' + chart.id + '_Chart-legend Mylegend" style="display:inline-block;position:relative">');
            text.push('<li class="labelTitle" style="color:Black;display:inline-block;float:left;">' + outerLabel + ': </li>');
            for (var k = 0; k < legsOuter.length; k++) {
                text.push('<li class="labels labels-outer" indexData="' + k + '" indexDataset="0" ><span style="background-color:' + legsOuter[k].color + ';"></span>');
                text.push(legsOuter[k].label);
                text.push('</li>');
            }
            text.push('<br>')
            text.push('<li class="labelTitle" style="color:Black;display:inline;float:left;">' + innerLabel + ': </li>');
            for (var k = 0; k < legsInner.length; k++) {
                text.push('<li class="labels" indexData="' + k + '" indexDataset="1"><span style="background-color:' + legsInner[k].color + ';"></span>');
                text.push(legsInner[k].label);
                text.push('</li>');
            }
            text.push('</ul>');

            return text.join("");
        },
        tooltips: {
            position:'cursor',
            backgroundColor: 'rgba(250,250,250,1)',
            xPadding: 10,
            caretPadding: 5,
            caretSize: 0,
            cornerRadius: 0,
            borderColor: 'rgba(150,150,150,1)',
            borderWidth: 1,
            bodyFontColor: 'rgba(0,0,0,1)',
            callbacks: {                                                                //tooltip callback showing what to display on hover
                label: function (tooltipItem, data) {
                    var dataset = data.datasets[tooltipItem.datasetIndex];
                    var index = tooltipItem.index;
                    return dataset.labels[index] + ": " + dataset.data[index];

                }
            }
        },
        legend: {
            display: false
        }
    }
}

//extension to draw labels on the donut elements, position them, and avoid chart blinking on hover
Chart.helpers.extend(Chart.controllers.doughnut.prototype,{

    draw: function () {

        myDoughnutExtend.apply(this, arguments);

        
        ctxDoughnut.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontFamily, 'normal', Chart.defaults.global.defaultFontFamily);
        ctxDoughnut.textAlign = 'center';
        ctxDoughnut.textBaseline = 'bottom';

        this.chart.data.datasets.forEach(function (dataset) {

            for (var i = 0; i < dataset.data.length; i++) {
                var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model,
                    total = dataset._meta[Object.keys(dataset._meta)[0]].total,
                    mid_radius = model.innerRadius + (model.outerRadius - model.innerRadius) / 2,
                    start_angle = model.startAngle,
                    end_angle = model.endAngle,
                    mid_angle = start_angle + (end_angle - start_angle) / 2;

                var x = mid_radius * Math.cos(mid_angle);
                var y = mid_radius * Math.sin(mid_angle);

                ctxDoughnut.fillStyle = '#fff';
                if (i == 3) { // Darker text color for lighter background
                    ctxDoughnut.fillStyle = '#444';
                }
                var percent = String(Math.round(dataset.data[i] / total * 100)) + "%";
                //Don't Display If Legend is hide or value is 0  ---- 
                if (dataset.data[i] != 0 && dataset._meta[Object.keys(dataset._meta)[0]].data[i].hidden != true) {
                    if (isValue == true) {
                        ctxDoughnut.fillText(dataset.data[i], model.x + x, model.y + y+6);
                    }
                    else if (isValue == false) {
                        // Display percent in another line, line break doesn't work for fillText
                        ctxDoughnut.fillText(percent, model.x + x, model.y + y+6);
                    }
                }
            }
        });
    }

})





//init chart with config options and type
chart = new Chart(ctxDoughnut, config);

//get legend container ID
var myLegendContainer = document.getElementById(chart_name_donut + '_LegendContainer');

//draw legend
$(myLegendContainer).html(chart.generateLegend());






//function to hide/show inner and outer donut parts
function legendClickCallback(event) {
    var event = event || window.event;

    var target = event.target || event.srcElement;
    while (target.nodeName !== 'LI') {
        target = target.parentElement;
    }
    var parent = target.parentElement;
    var chartId = parseInt(parent.classList[0].split("-")[0], 10);
    var chart = Chart.instances[chartId];
    var indexDataset = parseInt($(target).attr('indexDataset'));
    var indexData = parseInt($(target).attr('indexData'));
    var indexStart = 0;
    var indexEnd = 0;
    var meta = chart.getDatasetMeta(indexDataset);
    var spanElement = $(target).children();
    var colorTarget = $(spanElement).css('background-color');

    //outer doughnut
    if (indexDataset == 0) {
        var retainHidden = [];
        meta.data[indexData].hidden = meta.data[indexData].hidden == false ? !chart.data.datasets[indexDataset].data[indexData].hidden : false;

        indexStart = indexData * innerkeyCount;
        indexEnd = indexStart + innerkeyCount;
        if (meta.data[indexData].hidden == true) {
            var metaInner = chart.getDatasetMeta(indexDataset + 1);
            for (var i = indexStart; i < indexEnd; i++) {
                metaInner.data[i].hidden = true;
            }
        }
        else if (meta.data[indexData].hidden == false) {
            var numberHidden = $('.donutHiddenElement span').length
            var metaInner = chart.getDatasetMeta(indexDataset + 1);
            if (numberHidden == 0) {
                for (var i = indexStart; i < indexEnd; i++) {
                    metaInner.data[i].hidden = false;
                }
            }
            else {
                for (var i = indexStart; i < indexEnd; i++) {
                    for (var j = 0; j < numberHidden; j++) {
                        spanElementColor = $('.donutHiddenElement span')[j];
                        if ($(spanElementColor).css('background-color') == metaInner.data[i]._model.backgroundColor) {
                            metaInner.data[i].hidden = true;
                            retainHidden.push(metaInner.data[i]);
                        }
                        else {
                            if (retainHidden.length == 0) {
                                metaInner.data[i].hidden = false;
                            }
                            else {
                                for (var k = 0; k < retainHidden.length; k++) {
                                    if (metaInner.data[i] == retainHidden[k]) {
                                        metaInner.data[i].hidden = true;
                                    }
                                    else {
                                        metaInner.data[i].hidden = false;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    //inner doughnut
    else {
        //add case here to see which outer element is hidden and which is not
        var shownOuterElements = [];
        var hiddenOuterElements = [];
        var metaOuter = chart.getDatasetMeta(indexDataset - 1);
        for (var i = 0; i < metaOuter.data.length; i++) {
            if (!metaOuter.data[i].hidden) {
                shownOuterElements.push(i);
            }
        }

        for (var i = 0; i < metaOuter.data.length; i++) {
            if (metaOuter.data[i].hidden) {
                hiddenOuterElements.push(i);
            }
        }

        //if the outer element is shown, change hidden status
        if (shownOuterElements.length != 0) {
            for (var indexOuter of shownOuterElements) {
                initialI = indexOuter * (meta.data.length / metaOuter.data.length);
                endI = initialI + (meta.data.length / metaOuter.data.length)

                for (var i = initialI; i < endI; i++) {
                    if (colorTarget === meta.data[i]._model.backgroundColor) {
                        meta.data[i].hidden = meta.data[i].hidden == false ? !chart.data.datasets[indexDataset].data[i].hidden : false;

                        if (meta.data[i].hidden == true) {
                            innerDataValue[i] = meta.controller._data[i];
                            metaOuter.controller._data[indexOuter] = metaOuter.controller._data[indexOuter] - meta.controller._data[i];
                        }
                        else if (meta.data[i].hidden == false) {
                                metaOuter.controller._data[indexOuter] = metaOuter.controller._data[indexOuter] + innerDataValue[i];
                                delete innerDataValue[i];

                        }
                    }
                }
            }
            shownOuterElements =[]
        }
        //if the outer element is hidden, keep hidden status but change data anyway
        if (hiddenOuterElements.length != 0) {
            for (var indexOuter of hiddenOuterElements) {
                initialI = indexOuter * (meta.data.length / metaOuter.data.length);
                endI = initialI + (meta.data.length / metaOuter.data.length)
                for (var i = initialI; i < endI; i++) {
                    if (colorTarget === meta.data[i]._model.backgroundColor) {
                        //bug here
                        if (!$(target).hasClass('donutHiddenElement')) {
                            innerDataValue[i] = meta.controller._data[i];
                            metaOuter.controller._data[indexOuter] = metaOuter.controller._data[indexOuter] - meta.controller._data[i];
                        }
                        else if ($(target).hasClass('donutHiddenElement')) {
                            metaOuter.controller._data[indexOuter] = metaOuter.controller._data[indexOuter] + innerDataValue[i];
                            delete innerDataValue[i];
                        }
                    }
                }
            }
            hiddenOuterElements = []
        }
        $(target).toggleClass('donutHiddenElement');
    }
    chart.update();
}

//hide outer donut elements by toggling class
$('.labels-outer').click(function () {
    $(this).toggleClass('hiddenElementOuter')
})


//apply legend callback (on click expand or collapse)
var legendItems = myLegendContainer.getElementsByClassName('labels')
for (var i = 0; i < legendItems.length; i += 1) {
    legendItems[i].addEventListener("click", legendClickCallback, false);
}




return chart;

Leave a comment