【问题标题】:How can I add functionality to Chartjs Doughnut chart custom legend如何向 Chartjs 圆环图自定义图例添加功能
【发布时间】:2020-10-24 00:51:17
【问题描述】:

我已经尝试了一段时间来弄清楚如何向我创建的图例添加功能。我基本上希望拥有与自定义图例相关联的原始 onclick 效果。此外,图表中的两个甜甜圈是相关的。外圈有M和F变量,决定了内圈接下来占用的空间(a、b、c是M和F的变量)

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

我希望我的解释很清楚。

这里是使用的数据集和生成的自定义图例的代码 sn-p:

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) 中编写的自定义点击操作,但无法让它工作或找出插入它的位置。

【问题讨论】:

    标签: javascript html css chart.js


    【解决方案1】:

    这是我能够找到的解决方案,适用于具有 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;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-23
      • 2020-08-08
      • 2021-07-20
      • 2022-12-24
      相关资源
      最近更新 更多