首页 > 解决方案 > 如何向 Chartjs 圆环图自定义图例添加功能

问题描述

我一直在尝试弄清楚如何为我创建的图例添加功能。我基本上希望拥有与自定义图例相关联的原始 onclick 效果。此外,图表中的两个甜甜圈是相关的。外圈有M和F变量,决定了内圈占据的后续空间(a、b、c是M和F的变量)在此处输入图像描述

因此,当单击图例中的 M 标签时,外部甜甜圈中对应的 M 区域以及与之关联的 A 和 B(在本例中)应该消失,其他应该重新排列以占据整个空间。另一方面,如果单击图例中的 A、B 或 C 标签,其余的应重新排列以占据整个空间。即如果点击“A”,B应该占据M下的整个空间,B和C占据F下的整个空间。

我希望我的解释很清楚。

这是使用的数据集和生成的自定义图例的代码片段:

var chart = new Chart(ctx, {
        // The type of chart we want to create
        type: 'doughnut',


        
        // The data for our dataset
        data: {
            
            datasets: [

                {

                    data: [2,3],
                    backgroundColor: ["blue", "green"],
                    label: 'doughnutOuter',
                    labels: ['M','F']
                },
                {
                    data: [1,1,1,1,1],
                    backgroundColor: ["red", "violet", "red", "violet",'yellow'],
                    label: 'doughnutInner',
                    labels: ['A', 'B', 'A', 'B','C']
                }
            ]
            
        },

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

                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;
                            }
                        }
                        else if (j == 1) {
                            if (!containsObject(newd, legsInner)) {
                                legsInner.push(newd);
                                innerLabel = legendLabel;
                            }
                        }
                    }
                }


                text.push('<ul class="Mylegend ' + chart_name + '_Chart-legend">');
                text.push('<li class="labelTitle" style="color:Black;">' + outerLabel + '</li>');
                for (var k = 0; k < legsOuter.length; k++) {
                    text.push('<li class="labels"><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;">' + innerLabel + '</li>');
                for (var k = 0; k < legsInner.length; k++) {
                    text.push('<li class="labels"><span style="background-color:' + legsInner[k].color + '"></span>');
                    text.push(legsInner[k].label);
                    text.push('</li>');
                }

                text.push('</ul>');
                return text.join("");
            },
            tooltips: {
                callbacks: {
                }
            },
            legend: {
                display: false
            }

        }
    });

我尝试应用他们的文档( https://www.chartjs.org/docs/latest/configuration/legend.html#html-legends )中编写的自定义点击操作,但无法让它工作或弄清楚插入的位置它。

标签: javascripthtmlcsschart.js

解决方案


这是我能够找到的解决方案,适用于具有 n 个数据集的圆环图。

   $('#' + 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;

推荐阅读