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;
Source:stackexchange.com