/* * * * (c) 2010-2020 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ 'use strict'; import H from './Globals.js'; import LegendSymbolMixin from '../mixins/legend-symbol.js'; import Point from './Point.js'; import U from './Utilities.js'; var addEvent = U.addEvent, clamp = U.clamp, defined = U.defined, fireEvent = U.fireEvent, isNumber = U.isNumber, merge = U.merge, pick = U.pick, relativeLength = U.relativeLength, seriesType = U.seriesType, setAnimation = U.setAnimation; import './ColumnSeries.js'; import '../mixins/centered-series.js'; import './Options.js'; import './Series.js'; var CenteredSeriesMixin = H.CenteredSeriesMixin, getStartAndEndRadians = CenteredSeriesMixin.getStartAndEndRadians, noop = H.noop, Series = H.Series, seriesTypes = H.seriesTypes; /** * Pie series type. * * @private * @class * @name Highcharts.seriesTypes.pie * * @augments Highcharts.Series */ seriesType('pie', 'line', /** * A pie chart is a circular graphic which is divided into slices to * illustrate numerical proportion. * * @sample highcharts/demo/pie-basic/ * Pie chart * * @extends plotOptions.line * @excluding animationLimit, boostThreshold, connectEnds, connectNulls, * cropThreshold, dashStyle, dataSorting, dragDrop, * findNearestPointBy, getExtremesFromAll, label, lineWidth, * marker, negativeColor, pointInterval, pointIntervalUnit, * pointPlacement, pointStart, softThreshold, stacking, step, * threshold, turboThreshold, zoneAxis, zones, dataSorting * @product highcharts * @optionparent plotOptions.pie */ { /** * @excluding legendItemClick * @apioption plotOptions.pie.events */ /** * Fires when the checkbox next to the point name in the legend is * clicked. One parameter, event, is passed to the function. The state * of the checkbox is found by event.checked. The checked item is found * by event.item. Return false to prevent the default action which is to * toggle the select state of the series. * * @sample {highcharts} highcharts/plotoptions/series-events-checkboxclick/ * Alert checkbox status * * @type {Function} * @since 1.2.0 * @product highcharts * @context Highcharts.Point * @apioption plotOptions.pie.events.checkboxClick */ /** * Fires when the legend item belonging to the pie point (slice) is * clicked. The `this` keyword refers to the point itself. One * parameter, `event`, is passed to the function, containing common * event information. The default action is to toggle the visibility of * the point. This can be prevented by calling `event.preventDefault()`. * * @sample {highcharts} highcharts/plotoptions/pie-point-events-legenditemclick/ * Confirm toggle visibility * * @type {Highcharts.PointLegendItemClickCallbackFunction} * @since 1.2.0 * @product highcharts * @apioption plotOptions.pie.point.events.legendItemClick */ /** * The center of the pie chart relative to the plot area. Can be * percentages or pixel values. The default behaviour (as of 3.0) is to * center the pie so that all slices and data labels are within the plot * area. As a consequence, the pie may actually jump around in a chart * with dynamic values, as the data labels move. In that case, the * center should be explicitly set, for example to `["50%", "50%"]`. * * @sample {highcharts} highcharts/plotoptions/pie-center/ * Centered at 100, 100 * * @type {Array<(number|string|null),(number|string|null)>} * @default [null, null] * @product highcharts * * @private */ center: [null, null], /** * The color of the pie series. A pie series is represented as an empty * circle if the total sum of its values is 0. Use this property to * define the color of its border. * * In styled mode, the color can be defined by the * [colorIndex](#plotOptions.series.colorIndex) option. Also, the series * color can be set with the `.highcharts-series`, * `.highcharts-color-{n}`, `.highcharts-{type}-series` or * `.highcharts-series-{n}` class, or individual classes given by the * `className` option. * * @sample {highcharts} highcharts/plotoptions/pie-emptyseries/ * Empty pie series * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @default #cccccc * @apioption plotOptions.pie.color */ /** * @product highcharts * * @private */ clip: false, /** * @ignore-option * * @private */ colorByPoint: true, /** * A series specific or series type specific color set to use instead * of the global [colors](#colors). * * @sample {highcharts} highcharts/demo/pie-monochrome/ * Set default colors for all pies * * @type {Array} * @since 3.0 * @product highcharts * @apioption plotOptions.pie.colors */ /** * @declare Highcharts.SeriesPieDataLabelsOptionsObject * @extends plotOptions.series.dataLabels * @excluding align, allowOverlap, inside, staggerLines, step * @private */ dataLabels: { /** * Alignment method for data labels. Possible values are: * * - `toPlotEdges`: Each label touches the nearest vertical edge of * the plot area. * * - `connectors`: Connectors have the same x position and the * widest label of each half (left & right) touches the nearest * vertical edge of the plot area. * * @sample {highcharts} highcharts/plotoptions/pie-datalabels-alignto-connectors/ * alignTo: connectors * @sample {highcharts} highcharts/plotoptions/pie-datalabels-alignto-plotedges/ * alignTo: plotEdges * * @type {string} * @since 7.0.0 * @product highcharts * @apioption plotOptions.pie.dataLabels.alignTo */ allowOverlap: true, /** * The color of the line connecting the data label to the pie slice. * The default color is the same as the point's color. * * In styled mode, the connector stroke is given in the * `.highcharts-data-label-connector` class. * * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorcolor/ * Blue connectors * @sample {highcharts} highcharts/css/pie-point/ * Styled connectors * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @since 2.1 * @product highcharts * @apioption plotOptions.pie.dataLabels.connectorColor */ /** * The distance from the data label to the connector. Note that * data labels also have a default `padding`, so in order for the * connector to touch the text, the `padding` must also be 0. * * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorpadding/ * No padding * * @since 2.1 * @product highcharts */ connectorPadding: 5, /** * Specifies the method that is used to generate the connector path. * Highcharts provides 3 built-in connector shapes: `'fixedOffset'` * (default), `'straight'` and `'crookedLine'`. Using * `'crookedLine'` has the most sense (in most of the cases) when * `'alignTo'` is set. * * Users can provide their own method by passing a function instead * of a String. 3 arguments are passed to the callback: * * - Object that holds the information about the coordinates of the * label (`x` & `y` properties) and how the label is located in * relation to the pie (`alignment` property). `alignment` can by * one of the following: * `'left'` (pie on the left side of the data label), * `'right'` (pie on the right side of the data label) or * `'center'` (data label overlaps the pie). * * - Object that holds the information about the position of the * connector. Its `touchingSliceAt` porperty tells the position * of the place where the connector touches the slice. * * - Data label options * * The function has to return an SVG path definition in array form * (see the example). * * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorshape-string/ * connectorShape is a String * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorshape-function/ * connectorShape is a function * * @type {string|Function} * @since 7.0.0 * @product highcharts */ connectorShape: 'fixedOffset', /** * The width of the line connecting the data label to the pie slice. * * In styled mode, the connector stroke width is given in the * `.highcharts-data-label-connector` class. * * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorwidth-disabled/ * Disable the connector * @sample {highcharts} highcharts/css/pie-point/ * Styled connectors * * @type {number} * @default 1 * @since 2.1 * @product highcharts * @apioption plotOptions.pie.dataLabels.connectorWidth */ /** * Works only if `connectorShape` is `'crookedLine'`. It defines how * far from the vertical plot edge the coonnector path should be * crooked. * * @sample {highcharts} highcharts/plotoptions/pie-datalabels-crookdistance/ * crookDistance set to 90% * * @since 7.0.0 * @product highcharts */ crookDistance: '70%', /** * The distance of the data label from the pie's edge. Negative * numbers put the data label on top of the pie slices. Can also be * defined as a percentage of pie's radius. Connectors are only * shown for data labels outside the pie. * * @sample {highcharts} highcharts/plotoptions/pie-datalabels-distance/ * Data labels on top of the pie * * @type {number|string} * @since 2.1 * @product highcharts */ distance: 30, enabled: true, formatter: function () { return this.point.isNull ? void 0 : this.point.name; }, /** * Whether to render the connector as a soft arc or a line with * sharp break. Works only if `connectorShape` equals to * `fixedOffset`. * * @sample {highcharts} highcharts/plotoptions/pie-datalabels-softconnector-true/ * Soft * @sample {highcharts} highcharts/plotoptions/pie-datalabels-softconnector-false/ * Non soft * * @since 2.1.7 * @product highcharts */ softConnector: true, /** * @sample {highcharts} highcharts/plotoptions/pie-datalabels-overflow * Long labels truncated with an ellipsis * @sample {highcharts} highcharts/plotoptions/pie-datalabels-overflow-wrap * Long labels are wrapped * * @type {Highcharts.CSSObject} * @apioption plotOptions.pie.dataLabels.style */ x: 0 }, /** * If the total sum of the pie's values is 0, the series is represented * as an empty circle . The `fillColor` option defines the color of that * circle. Use [pie.borderWidth](#plotOptions.pie.borderWidth) to set * the border thickness. * * @sample {highcharts} highcharts/plotoptions/pie-emptyseries/ * Empty pie series * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @private */ fillColor: void 0, /** * The end angle of the pie in degrees where 0 is top and 90 is right. * Defaults to `startAngle` plus 360. * * @sample {highcharts} highcharts/demo/pie-semi-circle/ * Semi-circle donut * * @type {number} * @since 1.3.6 * @product highcharts * @apioption plotOptions.pie.endAngle */ /** * Equivalent to [chart.ignoreHiddenSeries](#chart.ignoreHiddenSeries), * this option tells whether the series shall be redrawn as if the * hidden point were `null`. * * The default value changed from `false` to `true` with Highcharts * 3.0. * * @sample {highcharts} highcharts/plotoptions/pie-ignorehiddenpoint/ * True, the hiddden point is ignored * * @since 2.3.0 * @product highcharts * * @private */ ignoreHiddenPoint: true, /** * @ignore-option * * @private */ inactiveOtherPoints: true, /** * The size of the inner diameter for the pie. A size greater than 0 * renders a donut chart. Can be a percentage or pixel value. * Percentages are relative to the pie size. Pixel values are given as * integers. * * * Note: in Highcharts < 4.1.2, the percentage was relative to the plot * area, not the pie size. * * @sample {highcharts} highcharts/plotoptions/pie-innersize-80px/ * 80px inner size * @sample {highcharts} highcharts/plotoptions/pie-innersize-50percent/ * 50% of the plot area * @sample {highcharts} highcharts/demo/3d-pie-donut/ * 3D donut * * @type {number|string} * @default 0 * @since 2.0 * @product highcharts * @apioption plotOptions.pie.innerSize */ /** * @ignore-option * * @private */ legendType: 'point', /** * @ignore-option * * @private */ marker: null, /** * The minimum size for a pie in response to auto margins. The pie will * try to shrink to make room for data labels in side the plot area, * but only to this size. * * @type {number|string} * @default 80 * @since 3.0 * @product highcharts * @apioption plotOptions.pie.minSize */ /** * The diameter of the pie relative to the plot area. Can be a * percentage or pixel value. Pixel values are given as integers. The * default behaviour (as of 3.0) is to scale to the plot area and give * room for data labels within the plot area. * [slicedOffset](#plotOptions.pie.slicedOffset) is also included in the * default size calculation. As a consequence, the size of the pie may * vary when points are updated and data labels more around. In that * case it is best to set a fixed value, for example `"75%"`. * * @sample {highcharts} highcharts/plotoptions/pie-size/ * Smaller pie * * @type {number|string|null} * @product highcharts * * @private */ size: null, /** * Whether to display this particular series or series type in the * legend. Since 2.1, pies are not shown in the legend by default. * * @sample {highcharts} highcharts/plotoptions/series-showinlegend/ * One series in the legend, one hidden * * @product highcharts * * @private */ showInLegend: false, /** * If a point is sliced, moved out from the center, how many pixels * should it be moved?. * * @sample {highcharts} highcharts/plotoptions/pie-slicedoffset-20/ * 20px offset * * @product highcharts * * @private */ slicedOffset: 10, /** * The start angle of the pie slices in degrees where 0 is top and 90 * right. * * @sample {highcharts} highcharts/plotoptions/pie-startangle-90/ * Start from right * * @type {number} * @default 0 * @since 2.3.4 * @product highcharts * @apioption plotOptions.pie.startAngle */ /** * Sticky tracking of mouse events. When true, the `mouseOut` event * on a series isn't triggered until the mouse moves over another * series, or out of the plot area. When false, the `mouseOut` event on * a series is triggered when the mouse leaves the area around the * series' graph or markers. This also implies the tooltip. When * `stickyTracking` is false and `tooltip.shared` is false, the tooltip * will be hidden when moving the mouse between series. * * @product highcharts * * @private */ stickyTracking: false, tooltip: { followPointer: true }, /** * The color of the border surrounding each slice. When `null`, the * border takes the same color as the slice fill. This can be used * together with a `borderWidth` to fill drawing gaps created by * antialiazing artefacts in borderless pies. * * In styled mode, the border stroke is given in the `.highcharts-point` * class. * * @sample {highcharts} highcharts/plotoptions/pie-bordercolor-black/ * Black border * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @default #ffffff * @product highcharts * * @private */ borderColor: '#ffffff', /** * The width of the border surrounding each slice. * * When setting the border width to 0, there may be small gaps between * the slices due to SVG antialiasing artefacts. To work around this, * keep the border width at 0.5 or 1, but set the `borderColor` to * `null` instead. * * In styled mode, the border stroke width is given in the * `.highcharts-point` class. * * @sample {highcharts} highcharts/plotoptions/pie-borderwidth/ * 3px border * * @product highcharts * * @private */ borderWidth: 1, /** * @ignore-options * @private */ lineWidth: void 0, states: { /** * @extends plotOptions.series.states.hover * @excluding marker, lineWidth, lineWidthPlus * @product highcharts */ hover: { /** * How much to brighten the point on interaction. Requires the * main color to be defined in hex or rgb(a) format. * * In styled mode, the hover brightness is by default replaced * by a fill-opacity given in the `.highcharts-point-hover` * class. * * @sample {highcharts} highcharts/plotoptions/pie-states-hover-brightness/ * Brightened by 0.5 * * @product highcharts */ brightness: 0.1 } } }, /* eslint-disable valid-jsdoc */ /** * @lends seriesTypes.pie.prototype */ { isCartesian: false, requireSorting: false, directTouch: true, noSharedTooltip: true, trackerGroups: ['group', 'dataLabelsGroup'], axisTypes: [], pointAttribs: seriesTypes.column.prototype.pointAttribs, /** * Animate the pies in * * @private * @function Highcharts.seriesTypes.pie#animate * * @param {boolean} [init=false] */ animate: function (init) { var series = this, points = series.points, startAngleRad = series.startAngleRad; if (!init) { points.forEach(function (point) { var 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); } }); } }, // Define hasData function for non-cartesian series. // Returns true if the series has points at all. hasData: function () { return !!this.processedXData.length; // != 0 }, /** * Recompute total chart sum and update percentages of points. * * @private * @function Highcharts.seriesTypes.pie#updateTotals * @return {void} */ updateTotals: function () { var i, total = 0, points = this.points, len = points.length, point, ignoreHiddenPoint = this.options.ignoreHiddenPoint; // Get the total sum for (i = 0; i < len; i++) { point = points[i]; total += (ignoreHiddenPoint && !point.visible) ? 0 : point.isNull ? 0 : 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; } }, /** * Extend the generatePoints method by adding total and percentage * properties to each point * * @private * @function Highcharts.seriesTypes.pie#generatePoints * @return {void} */ generatePoints: function () { Series.prototype.generatePoints.call(this); this.updateTotals(); }, /** * Utility for getting the x value from a given y, used for * anticollision logic in data labels. Added point for using specific * points' label distance. * @private */ getX: function (y, left, point) { var center = this.center, // Variable pie has individual radius radius = this.radii ? this.radii[point.index] : center[2] / 2, angle, x; angle = Math.asin(clamp((y - center[1]) / (radius + point.labelDistance), -1, 1)); x = center[0] + (left ? -1 : 1) * (Math.cos(angle) * (radius + point.labelDistance)) + (point.labelDistance > 0 ? (left ? -1 : 1) * this.options.dataLabels.padding : 0); return x; }, /** * Do translation for pie slices * * @private * @function Highcharts.seriesTypes.pie#translate * @param {Array} [positions] * @return {void} */ translate: function (positions) { this.generatePoints(); var series = this, cumulative = 0, precision = 1000, // issue #172 options = series.options, slicedOffset = options.slicedOffset, connectorOffset = slicedOffset + (options.borderWidth || 0), finalConnectorOffset, start, end, angle, 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, // the x component of the radius vector for a given point radiusX, radiusY, labelDistance = options.dataLabels.distance, ignoreHiddenPoint = options.ignoreHiddenPoint, i, len = points.length, point; // 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) { 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 (!ignoreHiddenPoint || point.visible) { cumulative += point.percentage / 100; } end = startAngleRad + (cumulative * circ); // set the shape point.shapeType = 'arc'; point.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 }; // Used for distance calculation for specific point. point.labelDistance = pick((point.options.dataLabels && point.options.dataLabels.distance), labelDistance); // Compute point.labelDistance if it's defined as percentage // of slice radius (#8854) point.labelDistance = relativeLength(point.labelDistance, point.shapeArgs.r); // Saved for later dataLabels distance calculation. series.maxLabelDistance = Math.max(series.maxLabelDistance || 0, point.labelDistance); // 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; // Set the anchor point for data labels. Use point.labelDistance // instead of labelDistance // #1174 // finalConnectorOffset - not override connectorOffset value. finalConnectorOffset = Math.min(connectorOffset, point.labelDistance / 5); // #1678 point.labelPosition = { natural: { // initial position of the data label - it's utilized for // finding the final position for the label x: positions[0] + radiusX + Math.cos(angle) * point.labelDistance, y: positions[1] + radiusY + Math.sin(angle) * point.labelDistance }, 'final': { // used for generating connector path - // initialized later in drawDataLabels function // x: undefined, // y: undefined }, // left - pie on the left side of the data label // right - pie on the right side of the data label // center - data label overlaps the pie alignment: point.labelDistance < 0 ? 'center' : point.half ? 'right' : 'left', connectorPosition: { breakAt: { x: positions[0] + radiusX + Math.cos(angle) * finalConnectorOffset, y: positions[1] + radiusY + Math.sin(angle) * finalConnectorOffset }, touchingSliceAt: { x: positions[0] + radiusX, y: positions[1] + radiusY } } }; } fireEvent(series, 'afterTranslate'); }, /** * 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 * @function Highcharts.seriesTypes.pie#drawEmpty */ drawEmpty: function () { var centerX, centerY, options = this.options; // Draw auxiliary graph if there're no visible points. if (this.total === 0) { centerX = this.center[0]; centerY = this.center[1]; if (!this.graph) { // Auxiliary graph doesn't exist yet. this.graph = this.chart.renderer.circle(centerX, centerY, 0) .addClass('highcharts-graph') .add(this.group); } this.graph.animate({ 'stroke-width': options.borderWidth, cx: centerX, cy: centerY, r: this.center[2] / 2, fill: options.fillColor || 'none', stroke: options.color || '#cccccc' }, this.options.animation); } else if (this.graph) { // Destroy the graph object. this.graph = this.graph.destroy(); } }, /** * Draw the data points * * @private * @function Highcharts.seriesTypes.pie#drawPoints * @return {void} */ redrawPoints: function () { var series = this, chart = series.chart, renderer = chart.renderer, groupTranslation, graphic, pointAttr, shapeArgs, shadow = series.options.shadow; this.drawEmpty(); if (shadow && !series.shadowGroup && !chart.styledMode) { series.shadowGroup = renderer.g('shadow') .attr({ zIndex: -1 }) .add(series.group); } // draw the slices series.points.forEach(function (point) { var 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) { // Put the shadow behind all points var shadowGroup = point.shadowGroup; if (shadow && !shadowGroup) { shadowGroup = point.shadowGroup = renderer .g('shadow') .add(series.shadowGroup); } if (shadowGroup) { shadowGroup.attr(groupTranslation); } 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' }) .shadow(shadow, shadowGroup); } point.delayedRendering = false; } graphic.attr({ visibility: point.visible ? 'inherit' : 'hidden' }); graphic.addClass(point.getClassName()); } else if (graphic) { point.graphic = graphic.destroy(); } }); }, /** * Slices in pie chart are initialized in DOM, but it's shapes and * animations are normally run in `drawPoints()`. * @private */ drawPoints: function () { var 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; } }); }, /** * @private * @deprecated * @function Highcharts.seriesTypes.pie#searchPoint */ searchPoint: noop, /** * Utility for sorting data labels * * @private * @function Highcharts.seriesTypes.pie#sortByAngle * @param {Array} points * @param {number} sign * @return {void} */ sortByAngle: function (points, sign) { points.sort(function (a, b) { return ((typeof a.angle !== 'undefined') && (b.angle - a.angle) * sign); }); }, /** * Use a simple symbol from LegendSymbolMixin. * * @private * @borrows Highcharts.LegendSymbolMixin.drawRectangle as Highcharts.seriesTypes.pie#drawLegendSymbol */ drawLegendSymbol: LegendSymbolMixin.drawRectangle, /** * Use the getCenter method from drawLegendSymbol. * * @private * @borrows Highcharts.CenteredSeriesMixin.getCenter as Highcharts.seriesTypes.pie#getCenter */ getCenter: CenteredSeriesMixin.getCenter, /** * Pies don't have point marker symbols. * * @deprecated * @private * @function Highcharts.seriesTypes.pie#getSymbol */ getSymbol: noop, /** * @private * @type {null} */ drawGraph: null }, /** * @lends seriesTypes.pie.prototype.pointClass.prototype */ { /** * Initialize the pie slice * * @private * @function Highcharts.seriesTypes.pie#pointClass#init * @return {Highcharts.Point} */ init: function () { Point.prototype.init.apply(this, arguments); var point = this, toggleSlice; point.name = pick(point.name, 'Slice'); // add event listener for select toggleSlice = function (e) { point.slice(e.type === 'select'); }; addEvent(point, 'select', toggleSlice); addEvent(point, 'unselect', toggleSlice); return point; }, /** * Negative points are not valid (#1530, #3623, #5322) * * @private * @function Highcharts.seriesTypes.pie#pointClass#isValid * @return {boolean} */ isValid: function () { return isNumber(this.y) && this.y >= 0; }, /** * Toggle the visibility of the pie slice * * @private * @function Highcharts.seriesTypes.pie#pointClass#setVisible * @param {boolean} vis * Whether to show the slice or not. If undefined, the visibility * is toggled. * @param {boolean} [redraw=false] * @return {void} */ setVisible: function (vis, redraw) { var point = this, series = point.series, chart = series.chart, ignoreHiddenPoint = series.options.ignoreHiddenPoint; redraw = pick(redraw, ignoreHiddenPoint); if (vis !== point.visible) { // If called without an argument, toggle visibility point.visible = point.options.visible = vis = typeof vis === 'undefined' ? !point.visible : vis; // update userOptions.data series.options.data[series.data.indexOf(point)] = point.options; // Show and hide associated elements. This is performed // regardless of redraw or not, because chart.redraw only // handles full series. ['graphic', 'dataLabel', 'connector', 'shadowGroup'].forEach(function (key) { if (point[key]) { point[key][vis ? 'show' : 'hide'](true); } }); if (point.legendItem) { chart.legend.colorizeItem(point, vis); } // #4170, hide halo after hiding point if (!vis && point.state === 'hover') { point.setState(''); } // Handle ignore hidden slices if (ignoreHiddenPoint) { series.isDirty = true; } if (redraw) { chart.redraw(); } } }, /** * Set or toggle whether the slice is cut out from the pie * * @private * @function Highcharts.seriesTypes.pie#pointClass#slice * @param {boolean} sliced * When undefined, the slice state is toggled. * @param {boolean} redraw * Whether to redraw the chart. True by default. * @param {boolean|Highcharts.AnimationOptionsObject} * Animation options. * @return {void} */ slice: function (sliced, redraw, animation) { var point = this, series = point.series, chart = series.chart; setAnimation(animation, chart); // redraw is true by default redraw = pick(redraw, true); /** * Pie series only. Whether to display a slice offset from the * center. * @name Highcharts.Point#sliced * @type {boolean|undefined} */ // if called without an argument, toggle point.sliced = point.options.sliced = sliced = defined(sliced) ? sliced : !point.sliced; // update userOptions.data series.options.data[series.data.indexOf(point)] = point.options; if (point.graphic) { point.graphic.animate(this.getTranslate()); } if (point.shadowGroup) { point.shadowGroup.animate(this.getTranslate()); } }, /** * @private * @function Highcharts.seriesTypes.pie#pointClass#getTranslate * @return {Highcharts.TranslationAttributes} */ getTranslate: function () { return this.sliced ? this.slicedTranslation : { translateX: 0, translateY: 0 }; }, /** * @private * @function Highcharts.seriesTypes.pie#pointClass#haloPath * @param {number} size * @return {Highcharts.SVGPathArray} */ haloPath: function (size) { var shapeArgs = this.shapeArgs; return this.sliced || !this.visible ? [] : this.series.chart.renderer.symbols.arc(shapeArgs.x, shapeArgs.y, shapeArgs.r + size, shapeArgs.r + size, { // Substract 1px to ensure the background is not bleeding // through between the halo and the slice (#7495). innerR: shapeArgs.r - 1, start: shapeArgs.start, end: shapeArgs.end }); }, connectorShapes: { // only one available before v7.0.0 fixedOffset: function (labelPosition, connectorPosition, options) { var breakAt = connectorPosition.breakAt, touchingSliceAt = connectorPosition.touchingSliceAt, lineSegment = options.softConnector ? [ 'C', // 1st control point (of the curve) labelPosition.x + // 5 gives the connector a little horizontal bend (labelPosition.alignment === 'left' ? -5 : 5), labelPosition.y, 2 * breakAt.x - touchingSliceAt.x, 2 * breakAt.y - touchingSliceAt.y, breakAt.x, breakAt.y // ] : [ 'L', breakAt.x, breakAt.y ]; // assemble the path return ([ ['M', labelPosition.x, labelPosition.y], lineSegment, ['L', touchingSliceAt.x, touchingSliceAt.y] ]); }, straight: function (labelPosition, connectorPosition) { var touchingSliceAt = connectorPosition.touchingSliceAt; // direct line to the slice return [ ['M', labelPosition.x, labelPosition.y], ['L', touchingSliceAt.x, touchingSliceAt.y] ]; }, crookedLine: function (labelPosition, connectorPosition, options) { var touchingSliceAt = connectorPosition.touchingSliceAt, series = this.series, pieCenterX = series.center[0], plotWidth = series.chart.plotWidth, plotLeft = series.chart.plotLeft, alignment = labelPosition.alignment, radius = this.shapeArgs.r, crookDistance = relativeLength(// % to fraction options.crookDistance, 1), crookX = alignment === 'left' ? pieCenterX + radius + (plotWidth + plotLeft - pieCenterX - radius) * (1 - crookDistance) : plotLeft + (pieCenterX - radius) * crookDistance, segmentWithCrook = [ 'L', crookX, labelPosition.y ], useCrook = true; // crookedLine formula doesn't make sense if the path overlaps // the label - use straight line instead in that case if (alignment === 'left' ? (crookX > labelPosition.x || crookX < touchingSliceAt.x) : (crookX < labelPosition.x || crookX > touchingSliceAt.x)) { useCrook = false; } // assemble the path var path = [ ['M', labelPosition.x, labelPosition.y] ]; if (useCrook) { path.push(segmentWithCrook); } path.push(['L', touchingSliceAt.x, touchingSliceAt.y]); return path; } }, /** * Extendable method for getting the path of the connector between the * data label and the pie slice. */ getConnectorPath: function () { var labelPosition = this.labelPosition, options = this.series.options.dataLabels, connectorShape = options.connectorShape, predefinedShapes = this.connectorShapes; // find out whether to use the predefined shape if (predefinedShapes[connectorShape]) { connectorShape = predefinedShapes[connectorShape]; } return connectorShape.call(this, { // pass simplified label position object for user's convenience x: labelPosition.final.x, y: labelPosition.final.y, alignment: labelPosition.alignment }, labelPosition.connectorPosition, options); } } /* eslint-enable valid-jsdoc */ ); /** * A `pie` series. If the [type](#series.pie.type) option is not specified, * it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.pie * @excluding dataParser, dataURL, stack, xAxis, yAxis, dataSorting, step * @product highcharts * @apioption series.pie */ /** * An array of data points for the series. For the `pie` series type, * points can be given in the following ways: * * 1. An array of numerical values. In this case, the numerical values will be * interpreted as `y` options. Example: * ```js * data: [0, 5, 3, 5] * ``` * * 2. An array of objects with named values. The following snippet shows only a * few settings, see the complete options set below. If the total number of * data points exceeds the series' * [turboThreshold](#series.pie.turboThreshold), * this option is not available. * ```js * data: [{ * y: 1, * name: "Point2", * color: "#00FF00" * }, { * y: 7, * name: "Point1", * color: "#FF00FF" * }] * ``` * * @sample {highcharts} highcharts/chart/reflow-true/ * Numerical values * @sample {highcharts} highcharts/series/data-array-of-arrays/ * Arrays of numeric x and y * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ * Arrays of datetime x and y * @sample {highcharts} highcharts/series/data-array-of-name-value/ * Arrays of point.name and y * @sample {highcharts} highcharts/series/data-array-of-objects/ * Config objects * * @type {Array|null|*>} * @extends series.line.data * @excluding marker, x * @product highcharts * @apioption series.pie.data */ /** * @type {Highcharts.SeriesPieDataLabelsOptionsObject} * @product highcharts * @apioption series.pie.data.dataLabels */ /** * The sequential index of the data point in the legend. * * @type {number} * @product highcharts * @apioption series.pie.data.legendIndex */ /** * Whether to display a slice offset from the center. * * @sample {highcharts} highcharts/point/sliced/ * One sliced point * * @type {boolean} * @product highcharts * @apioption series.pie.data.sliced */ /** * @excluding legendItemClick * @product highcharts * @apioption series.pie.events */ ''; // placeholder for transpiled doclets above