import DatasetController from '../core/core.datasetController'; import {toRadians, PI} from '../helpers/index'; export default class PolarAreaController extends DatasetController { constructor(chart, datasetIndex) { super(chart, datasetIndex); this.innerRadius = undefined; this.outerRadius = undefined; } update(mode) { const arcs = this._cachedMeta.data; this._updateRadius(); this.updateElements(arcs, 0, arcs.length, mode); } /** * @private */ _updateRadius() { const me = this; const chart = me.chart; const chartArea = chart.chartArea; const opts = chart.options; const minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); const outerRadius = Math.max(minSize / 2, 0); const innerRadius = Math.max(opts.cutoutPercentage ? (outerRadius / 100) * (opts.cutoutPercentage) : 1, 0); const radiusLength = (outerRadius - innerRadius) / chart.getVisibleDatasetCount(); me.outerRadius = outerRadius - (radiusLength * me.index); me.innerRadius = me.outerRadius - radiusLength; } updateElements(arcs, start, count, mode) { const me = this; const reset = mode === 'reset'; const chart = me.chart; const dataset = me.getDataset(); const opts = chart.options; const animationOpts = opts.animation; const scale = me._cachedMeta.rScale; const centerX = scale.xCenter; const centerY = scale.yCenter; const datasetStartAngle = scale.getIndexAngle(0) - 0.5 * PI; let angle = datasetStartAngle; let i; const defaultAngle = 360 / me.countVisibleElements(); for (i = 0; i < start; ++i) { angle += me._computeAngle(i, mode, defaultAngle); } for (i = start; i < start + count; i++) { const arc = arcs[i]; let startAngle = angle; let endAngle = angle + me._computeAngle(i, mode, defaultAngle); let outerRadius = chart.getDataVisibility(i) ? scale.getDistanceFromCenterForValue(dataset.data[i]) : 0; angle = endAngle; if (reset) { if (animationOpts.animateScale) { outerRadius = 0; } if (animationOpts.animateRotate) { startAngle = endAngle = datasetStartAngle; } } const properties = { x: centerX, y: centerY, innerRadius: 0, outerRadius, startAngle, endAngle, options: me.resolveDataElementOptions(i, mode) }; me.updateElement(arc, i, properties, mode); } } countVisibleElements() { const dataset = this.getDataset(); const meta = this._cachedMeta; let count = 0; meta.data.forEach((element, index) => { if (!isNaN(dataset.data[index]) && this.chart.getDataVisibility(index)) { count++; } }); return count; } /** * @private */ _computeAngle(index, mode, defaultAngle) { return this.chart.getDataVisibility(index) ? toRadians(this.resolveDataElementOptions(index, mode).angle || defaultAngle) : 0; } } PolarAreaController.id = 'polarArea'; /** * @type {any} */ PolarAreaController.defaults = { dataElementType: 'arc', animation: { animateRotate: true, animateScale: true }, animations: { numbers: { type: 'number', properties: ['x', 'y', 'startAngle', 'endAngle', 'innerRadius', 'outerRadius'] }, }, indexAxis: 'r', startAngle: 0, }; /** * @type {any} */ PolarAreaController.overrides = { aspectRatio: 1, plugins: { legend: { labels: { generateLabels(chart) { const data = chart.data; if (data.labels.length && data.datasets.length) { return data.labels.map((label, i) => { const meta = chart.getDatasetMeta(0); const style = meta.controller.getStyle(i); return { text: label, fillStyle: style.backgroundColor, strokeStyle: style.borderColor, lineWidth: style.borderWidth, hidden: !chart.getDataVisibility(i), // Extra data used for toggling the correct item index: i }; }); } return []; } }, onClick(e, legendItem, legend) { legend.chart.toggleDataVisibility(legendItem.index); legend.chart.update(); } }, // Need to override these to give a nice default tooltip: { callbacks: { title() { return ''; }, label(context) { return context.chart.data.labels[context.dataIndex] + ': ' + context.formattedValue; } } } }, scales: { r: { type: 'radialLinear', angleLines: { display: false }, beginAtZero: true, grid: { circular: true }, pointLabels: { display: false }, startAngle: 0 } } };