mirror of
https://github.com/chartjs/Chart.js.git
synced 2024-10-08 13:19:07 +02:00
29115c9d2c
New Chart.Element.hidden bool flag storing the visibility state of its associated data. Since elements belong to a specific chart, this change allows to manage data visibility per chart (e.g. when clicking the legend of some charts). This commit also changes (fixes?) the polar chart animation when data visibility changes. Previous implementation was affected by an edge effect due to the use of NaN as hidden implementation.
290 lines
12 KiB
JavaScript
290 lines
12 KiB
JavaScript
"use strict";
|
|
|
|
module.exports = function(Chart) {
|
|
|
|
var helpers = Chart.helpers;
|
|
|
|
Chart.defaults.doughnut = {
|
|
animation: {
|
|
//Boolean - Whether we animate the rotation of the Doughnut
|
|
animateRotate: true,
|
|
//Boolean - Whether we animate scaling the Doughnut from the centre
|
|
animateScale: false
|
|
},
|
|
aspectRatio: 1,
|
|
hover: {
|
|
mode: 'single'
|
|
},
|
|
legendCallback: function(chart) {
|
|
var text = [];
|
|
text.push('<ul class="' + chart.id + '-legend">');
|
|
|
|
if (chart.data.datasets.length) {
|
|
for (var i = 0; i < chart.data.datasets[0].data.length; ++i) {
|
|
text.push('<li><span style="background-color:' + chart.data.datasets[0].backgroundColor[i] + '"></span>');
|
|
if (chart.data.labels[i]) {
|
|
text.push(chart.data.labels[i]);
|
|
}
|
|
text.push('</li>');
|
|
}
|
|
}
|
|
|
|
text.push('</ul>');
|
|
return text.join("");
|
|
},
|
|
legend: {
|
|
labels: {
|
|
generateLabels: function(chart) {
|
|
var data = chart.data;
|
|
if (data.labels.length && data.datasets.length) {
|
|
return data.labels.map(function(label, i) {
|
|
var meta = chart.getDatasetMeta(0);
|
|
var ds = data.datasets[0];
|
|
var arc = meta.data[i];
|
|
var fill = arc.custom && arc.custom.backgroundColor ? arc.custom.backgroundColor : helpers.getValueAtIndexOrDefault(ds.backgroundColor, i, this.chart.options.elements.arc.backgroundColor);
|
|
var stroke = arc.custom && arc.custom.borderColor ? arc.custom.borderColor : helpers.getValueAtIndexOrDefault(ds.borderColor, i, this.chart.options.elements.arc.borderColor);
|
|
var bw = arc.custom && arc.custom.borderWidth ? arc.custom.borderWidth : helpers.getValueAtIndexOrDefault(ds.borderWidth, i, this.chart.options.elements.arc.borderWidth);
|
|
|
|
return {
|
|
text: label,
|
|
fillStyle: fill,
|
|
strokeStyle: stroke,
|
|
lineWidth: bw,
|
|
hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
|
|
|
|
// Extra data used for toggling the correct item
|
|
index: i
|
|
};
|
|
}, this);
|
|
} else {
|
|
return [];
|
|
}
|
|
}
|
|
},
|
|
|
|
onClick: function(e, legendItem) {
|
|
var index = legendItem.index;
|
|
var chart = this.chart;
|
|
var i, ilen, meta;
|
|
|
|
for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
|
|
meta = chart.getDatasetMeta(i);
|
|
meta.data[index].hidden = !meta.data[index].hidden;
|
|
}
|
|
|
|
chart.update();
|
|
}
|
|
},
|
|
|
|
//The percentage of the chart that we cut out of the middle.
|
|
cutoutPercentage: 50,
|
|
|
|
//The rotation of the chart, where the first data arc begins.
|
|
rotation: Math.PI * -0.5,
|
|
|
|
//The total circumference of the chart.
|
|
circumference: Math.PI * 2.0,
|
|
|
|
// Need to override these to give a nice default
|
|
tooltips: {
|
|
callbacks: {
|
|
title: function() {
|
|
return '';
|
|
},
|
|
label: function(tooltipItem, data) {
|
|
return data.labels[tooltipItem.index] + ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
Chart.defaults.pie = helpers.clone(Chart.defaults.doughnut);
|
|
helpers.extend(Chart.defaults.pie, {
|
|
cutoutPercentage: 0
|
|
});
|
|
|
|
|
|
Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({
|
|
linkScales: function() {
|
|
// no scales for doughnut
|
|
},
|
|
|
|
addElements: function() {
|
|
var meta = this.getMeta();
|
|
helpers.each(this.getDataset().data, function(value, index) {
|
|
meta.data[index] = meta.data[index] || new Chart.elements.Arc({
|
|
_chart: this.chart.chart,
|
|
_datasetIndex: this.index,
|
|
_index: index
|
|
});
|
|
}, this);
|
|
},
|
|
|
|
addElementAndReset: function(index, colorForNewElement) {
|
|
var arc = new Chart.elements.Arc({
|
|
_chart: this.chart.chart,
|
|
_datasetIndex: this.index,
|
|
_index: index
|
|
});
|
|
|
|
if (colorForNewElement && helpers.isArray(this.getDataset().backgroundColor)) {
|
|
this.getDataset().backgroundColor.splice(index, 0, colorForNewElement);
|
|
}
|
|
|
|
// Add to the points array and reset it
|
|
this.getMeta().data.splice(index, 0, arc);
|
|
this.updateElement(arc, index, true);
|
|
},
|
|
|
|
// Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly
|
|
getRingIndex: function getRingIndex(datasetIndex) {
|
|
var ringIndex = 0;
|
|
|
|
for (var j = 0; j < datasetIndex; ++j) {
|
|
if (this.chart.isDatasetVisible(j)) {
|
|
++ringIndex;
|
|
}
|
|
}
|
|
|
|
return ringIndex;
|
|
},
|
|
|
|
update: function update(reset) {
|
|
var availableWidth = this.chart.chartArea.right - this.chart.chartArea.left - this.chart.options.elements.arc.borderWidth;
|
|
var availableHeight = this.chart.chartArea.bottom - this.chart.chartArea.top - this.chart.options.elements.arc.borderWidth;
|
|
var minSize = Math.min(availableWidth, availableHeight);
|
|
var offset = {x: 0, y: 0};
|
|
|
|
// If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc
|
|
if (this.chart.options.circumference < Math.PI * 2.0) {
|
|
var startAngle = this.chart.options.rotation % (Math.PI * 2.0);
|
|
startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0);
|
|
var endAngle = startAngle + this.chart.options.circumference;
|
|
var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)};
|
|
var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)};
|
|
var contains0 = (startAngle <= 0 && 0 <= endAngle) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle);
|
|
var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle);
|
|
var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle);
|
|
var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle);
|
|
var cutout = this.chart.options.cutoutPercentage / 100.0;
|
|
var min = {x: contains180 ? -1 : Math.min(start.x * (start.x < 0 ? 1 : cutout), end.x * (end.x < 0 ? 1 : cutout)), y: contains270 ? -1 : Math.min(start.y * (start.y < 0 ? 1 : cutout), end.y * (end.y < 0 ? 1 : cutout))};
|
|
var max = {x: contains0 ? 1 : Math.max(start.x * (start.x > 0 ? 1 : cutout), end.x * (end.x > 0 ? 1 : cutout)), y: contains90 ? 1 : Math.max(start.y * (start.y > 0 ? 1 : cutout), end.y * (end.y > 0 ? 1 : cutout))};
|
|
var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5};
|
|
minSize = Math.min(availableWidth / size.width, availableHeight / size.height);
|
|
offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5};
|
|
}
|
|
|
|
this.chart.outerRadius = Math.max(minSize / 2, 0);
|
|
this.chart.innerRadius = Math.max(this.chart.options.cutoutPercentage ? (this.chart.outerRadius / 100) * (this.chart.options.cutoutPercentage) : 1, 0);
|
|
this.chart.radiusLength = (this.chart.outerRadius - this.chart.innerRadius) / this.chart.getVisibleDatasetCount();
|
|
this.chart.offsetX = offset.x * this.chart.outerRadius;
|
|
this.chart.offsetY = offset.y * this.chart.outerRadius;
|
|
|
|
this.getMeta().total = this.calculateTotal();
|
|
|
|
this.outerRadius = this.chart.outerRadius - (this.chart.radiusLength * this.getRingIndex(this.index));
|
|
this.innerRadius = this.outerRadius - this.chart.radiusLength;
|
|
|
|
helpers.each(this.getMeta().data, function(arc, index) {
|
|
this.updateElement(arc, index, reset);
|
|
}, this);
|
|
},
|
|
|
|
updateElement: function(arc, index, reset) {
|
|
var centerX = (this.chart.chartArea.left + this.chart.chartArea.right) / 2;
|
|
var centerY = (this.chart.chartArea.top + this.chart.chartArea.bottom) / 2;
|
|
var startAngle = this.chart.options.rotation; // non reset case handled later
|
|
var endAngle = this.chart.options.rotation; // non reset case handled later
|
|
var circumference = reset && this.chart.options.animation.animateRotate ? 0 : arc.hidden? 0 : this.calculateCircumference(this.getDataset().data[index]) * (this.chart.options.circumference / (2.0 * Math.PI));
|
|
var innerRadius = reset && this.chart.options.animation.animateScale ? 0 : this.innerRadius;
|
|
var outerRadius = reset && this.chart.options.animation.animateScale ? 0 : this.outerRadius;
|
|
|
|
helpers.extend(arc, {
|
|
// Utility
|
|
_chart: this.chart.chart,
|
|
_datasetIndex: this.index,
|
|
_index: index,
|
|
|
|
// Desired view properties
|
|
_model: {
|
|
x: centerX + this.chart.offsetX,
|
|
y: centerY + this.chart.offsetY,
|
|
startAngle: startAngle,
|
|
endAngle: endAngle,
|
|
circumference: circumference,
|
|
outerRadius: outerRadius,
|
|
innerRadius: innerRadius,
|
|
|
|
backgroundColor: arc.custom && arc.custom.backgroundColor ? arc.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.arc.backgroundColor),
|
|
hoverBackgroundColor: arc.custom && arc.custom.hoverBackgroundColor ? arc.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().hoverBackgroundColor, index, this.chart.options.elements.arc.hoverBackgroundColor),
|
|
borderWidth: arc.custom && arc.custom.borderWidth ? arc.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.arc.borderWidth),
|
|
borderColor: arc.custom && arc.custom.borderColor ? arc.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.arc.borderColor),
|
|
|
|
label: helpers.getValueAtIndexOrDefault(this.getDataset().label, index, this.chart.data.labels[index])
|
|
}
|
|
});
|
|
|
|
// Set correct angles if not resetting
|
|
if (!reset) {
|
|
|
|
if (index === 0) {
|
|
arc._model.startAngle = this.chart.options.rotation;
|
|
} else {
|
|
arc._model.startAngle = this.getMeta().data[index - 1]._model.endAngle;
|
|
}
|
|
|
|
arc._model.endAngle = arc._model.startAngle + arc._model.circumference;
|
|
}
|
|
|
|
arc.pivot();
|
|
},
|
|
|
|
draw: function(ease) {
|
|
var easingDecimal = ease || 1;
|
|
helpers.each(this.getMeta().data, function(arc, index) {
|
|
arc.transition(easingDecimal).draw();
|
|
});
|
|
},
|
|
|
|
setHoverStyle: function(arc) {
|
|
var dataset = this.chart.data.datasets[arc._datasetIndex];
|
|
var index = arc._index;
|
|
|
|
arc._model.backgroundColor = arc.custom && arc.custom.hoverBackgroundColor ? arc.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(arc._model.backgroundColor).saturate(0.5).darken(0.1).rgbString());
|
|
arc._model.borderColor = arc.custom && arc.custom.hoverBorderColor ? arc.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(arc._model.borderColor).saturate(0.5).darken(0.1).rgbString());
|
|
arc._model.borderWidth = arc.custom && arc.custom.hoverBorderWidth ? arc.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, arc._model.borderWidth);
|
|
},
|
|
|
|
removeHoverStyle: function(arc) {
|
|
var dataset = this.chart.data.datasets[arc._datasetIndex];
|
|
var index = arc._index;
|
|
|
|
arc._model.backgroundColor = arc.custom && arc.custom.backgroundColor ? arc.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.arc.backgroundColor);
|
|
arc._model.borderColor = arc.custom && arc.custom.borderColor ? arc.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.arc.borderColor);
|
|
arc._model.borderWidth = arc.custom && arc.custom.borderWidth ? arc.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.arc.borderWidth);
|
|
},
|
|
|
|
calculateTotal: function() {
|
|
var meta = this.getMeta();
|
|
var total = 0;
|
|
|
|
this.getDataset().data.forEach(function(value, index) {
|
|
if (!isNaN(value) && !meta.data[index].hidden) {
|
|
total += Math.abs(value);
|
|
}
|
|
});
|
|
|
|
return total;
|
|
},
|
|
|
|
calculateCircumference: function(value) {
|
|
var total = this.getMeta().total;
|
|
if (total > 0 && !isNaN(value)) {
|
|
return (Math.PI * 2.0) * (value / total);
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
});
|
|
};
|