UNPKG

highcharts

Version:
366 lines (365 loc) 12.9 kB
/* * * * (c) 2010-2025 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ 'use strict'; import CU from '../CenteredUtilities.js'; const { getStartAndEndRadians } = CU; import ColumnSeries from '../Column/ColumnSeries.js'; import H from '../../Core/Globals.js'; const { noop } = H; import PiePoint from './PiePoint.js'; import PieSeriesDefaults from './PieSeriesDefaults.js'; import Series from '../../Core/Series/Series.js'; import SeriesRegistry from '../../Core/Series/SeriesRegistry.js'; import Symbols from '../../Core/Renderer/SVG/Symbols.js'; import U from '../../Core/Utilities.js'; const { clamp, extend, fireEvent, merge, pick } = U; /* * * * Class * * */ /** * Pie series type. * * @private * @class * @name Highcharts.seriesTypes.pie * * @augments Highcharts.Series */ class PieSeries extends Series { /* * * * Functions * * */ /* eslint-disable valid-jsdoc */ /** * Animates the pies in. * @private */ animate(init) { const series = this, points = series.points, startAngleRad = series.startAngleRad; if (!init) { points.forEach(function (point) { const graphic = point.graphic, args = point.shapeArgs; if (graphic && args) { // Start values graphic.attr({ // Animate from inner radius (#779) r: pick(point.startR, (series.center && series.center[3] / 2)), start: startAngleRad, end: startAngleRad }); // Animate graphic.animate({ r: args.r, start: args.start, end: args.end }, series.options.animation); } }); } } /** * Called internally to draw auxiliary graph in pie-like series in * situtation when the default graph is not sufficient enough to present * the data well. Auxiliary graph is saved in the same object as * regular graph. * @private */ drawEmpty() { const start = this.startAngleRad, end = this.endAngleRad, options = this.options; let centerX, centerY; // Draw auxiliary graph if there're no visible points. if (this.total === 0 && this.center) { centerX = this.center[0]; centerY = this.center[1]; if (!this.graph) { this.graph = this.chart.renderer .arc(centerX, centerY, this.center[1] / 2, 0, start, end) .addClass('highcharts-empty-series') .add(this.group); } this.graph.attr({ d: Symbols.arc(centerX, centerY, this.center[2] / 2, 0, { start, end, innerR: this.center[3] / 2 }) }); if (!this.chart.styledMode) { this.graph.attr({ 'stroke-width': options.borderWidth, fill: options.fillColor || 'none', stroke: options.color || "#cccccc" /* Palette.neutralColor20 */ }); } } else if (this.graph) { // Destroy the graph object. this.graph = this.graph.destroy(); } } /** * Slices in pie chart are initialized in DOM, but it's shapes and * animations are normally run in `drawPoints()`. * @private */ drawPoints() { const renderer = this.chart.renderer; this.points.forEach(function (point) { // When updating a series between 2d and 3d or cartesian and // polar, the shape type changes. if (point.graphic && point.hasNewShapeType()) { point.graphic = point.graphic.destroy(); } if (!point.graphic) { point.graphic = renderer[point.shapeType](point.shapeArgs) .add(point.series.group); point.delayedRendering = true; } }); } /** * Extend the generatePoints method by adding total and percentage * properties to each point * @private */ generatePoints() { super.generatePoints(); this.updateTotals(); } /** * Utility for getting the x value from a given y, used for anticollision * logic in data labels. * @private */ getX(y, left, point, dataLabel) { const center = this.center, // Variable pie has individual radius radius = this.radii ? this.radii[point.index] || 0 : center[2] / 2, labelPosition = dataLabel.dataLabelPosition, distance = labelPosition?.distance || 0; const angle = Math.asin(clamp((y - center[1]) / (radius + distance), -1, 1)); const x = center[0] + (left ? -1 : 1) * (Math.cos(angle) * (radius + distance)) + (distance > 0 ? (left ? -1 : 1) * (dataLabel.padding || 0) : 0); return x; } /** * Define hasData function for non-cartesian series. Returns true if the * series has points at all. * @private */ hasData() { return !!this.dataTable.rowCount; } /** * Draw the data points * @private */ redrawPoints() { const series = this, chart = series.chart; let groupTranslation, graphic, pointAttr, shapeArgs; this.drawEmpty(); // Apply the drop-shadow to the group because otherwise each element // would cast a shadow on others if (series.group && !chart.styledMode) { series.group.shadow(series.options.shadow); } // Draw the slices series.points.forEach(function (point) { const animateTo = {}; graphic = point.graphic; if (!point.isNull && graphic) { shapeArgs = point.shapeArgs; // If the point is sliced, use special translation, else use // plot area translation groupTranslation = point.getTranslate(); if (!chart.styledMode) { pointAttr = series.pointAttribs(point, (point.selected && 'select')); } // Draw the slice if (!point.delayedRendering) { graphic .setRadialReference(series.center); if (!chart.styledMode) { merge(true, animateTo, pointAttr); } merge(true, animateTo, shapeArgs, groupTranslation); graphic.animate(animateTo); } else { graphic .setRadialReference(series.center) .attr(shapeArgs) .attr(groupTranslation); if (!chart.styledMode) { graphic .attr(pointAttr) .attr({ 'stroke-linejoin': 'round' }); } point.delayedRendering = false; } graphic .attr({ visibility: point.visible ? 'inherit' : 'hidden' }); graphic.addClass(point.getClassName(), true); } else if (graphic) { point.graphic = graphic.destroy(); } }); } /** * Utility for sorting data labels. * @private */ sortByAngle(points, sign) { points.sort(function (a, b) { return ((typeof a.angle !== 'undefined') && (b.angle - a.angle) * sign); }); } /** * Do translation for pie slices * @private */ translate(positions) { fireEvent(this, 'translate'); this.generatePoints(); const series = this, precision = 1000, // Issue #172 options = series.options, slicedOffset = options.slicedOffset, radians = getStartAndEndRadians(options.startAngle, options.endAngle), startAngleRad = series.startAngleRad = radians.start, endAngleRad = series.endAngleRad = radians.end, circ = endAngleRad - startAngleRad, // 2 * Math.PI, points = series.points, ignoreHiddenPoint = options.ignoreHiddenPoint, len = points.length; let start, end, angle, // The x component of the radius vector for a given point radiusX, radiusY, i, point, cumulative = 0; // Get positions - either an integer or a percentage string must be // given. If positions are passed as a parameter, we're in a // recursive loop for adjusting space for data labels. if (!positions) { /** * The series center position, read only. This applies only to * circular chart types like pie and sunburst. It is an array of * `[centerX, centerY, diameter, innerDiameter]`. * * @name Highcharts.Series#center * @type {Array<number>} */ series.center = positions = series.getCenter(); } // Calculate the geometry for each point for (i = 0; i < len; i++) { point = points[i]; // Set start and end angle start = startAngleRad + (cumulative * circ); if (point.isValid() && (!ignoreHiddenPoint || point.visible)) { cumulative += point.percentage / 100; } end = startAngleRad + (cumulative * circ); // Set the shape const shapeArgs = { x: positions[0], y: positions[1], r: positions[2] / 2, innerR: positions[3] / 2, start: Math.round(start * precision) / precision, end: Math.round(end * precision) / precision }; point.shapeType = 'arc'; point.shapeArgs = shapeArgs; // The angle must stay within -90 and 270 (#2645) angle = (end + start) / 2; if (angle > 1.5 * Math.PI) { angle -= 2 * Math.PI; } else if (angle < -Math.PI / 2) { angle += 2 * Math.PI; } // Center for the sliced out slice point.slicedTranslation = { translateX: Math.round(Math.cos(angle) * slicedOffset), translateY: Math.round(Math.sin(angle) * slicedOffset) }; // Set the anchor point for tooltips radiusX = Math.cos(angle) * positions[2] / 2; radiusY = Math.sin(angle) * positions[2] / 2; point.tooltipPos = [ positions[0] + radiusX * 0.7, positions[1] + radiusY * 0.7 ]; point.half = angle < -Math.PI / 2 || angle > Math.PI / 2 ? 1 : 0; point.angle = angle; } fireEvent(series, 'afterTranslate'); } /** * Recompute total chart sum and update percentages of points. * @private */ updateTotals() { const points = this.points, len = points.length, ignoreHiddenPoint = this.options.ignoreHiddenPoint; let i, point, total = 0; // Get the total sum for (i = 0; i < len; i++) { point = points[i]; if (point.isValid() && (!ignoreHiddenPoint || point.visible)) { total += point.y; } } this.total = total; // Set each point's properties for (i = 0; i < len; i++) { point = points[i]; point.percentage = (total > 0 && (point.visible || !ignoreHiddenPoint)) ? point.y / total * 100 : 0; point.total = total; } } } /* * * * Static Properties * * */ PieSeries.defaultOptions = merge(Series.defaultOptions, PieSeriesDefaults); extend(PieSeries.prototype, { axisTypes: [], directTouch: true, drawGraph: void 0, drawTracker: ColumnSeries.prototype.drawTracker, getCenter: CU.getCenter, getSymbol: noop, invertible: false, isCartesian: false, noSharedTooltip: true, pointAttribs: ColumnSeries.prototype.pointAttribs, pointClass: PiePoint, requireSorting: false, searchPoint: noop, trackerGroups: ['group', 'dataLabelsGroup'] }); SeriesRegistry.registerSeriesType('pie', PieSeries); /* * * * Default Export * * */ export default PieSeries;