PieSeries.js 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327
  1. /* *
  2. * (c) 2010-2019 Torstein Honsi
  3. *
  4. * License: www.highcharts.com/license
  5. */
  6. 'use strict';
  7. import H from './Globals.js';
  8. import './Utilities.js';
  9. import './ColumnSeries.js';
  10. import '../mixins/centered-series.js';
  11. import './Legend.js';
  12. import './Options.js';
  13. import './Point.js';
  14. import './Series.js';
  15. var addEvent = H.addEvent,
  16. CenteredSeriesMixin = H.CenteredSeriesMixin,
  17. defined = H.defined,
  18. extend = H.extend,
  19. getStartAndEndRadians = CenteredSeriesMixin.getStartAndEndRadians,
  20. LegendSymbolMixin = H.LegendSymbolMixin,
  21. noop = H.noop,
  22. pick = H.pick,
  23. Point = H.Point,
  24. Series = H.Series,
  25. seriesType = H.seriesType,
  26. seriesTypes = H.seriesTypes,
  27. setAnimation = H.setAnimation;
  28. /**
  29. * Pie series type.
  30. *
  31. * @private
  32. * @class
  33. * @name Highcharts.seriesTypes.pie
  34. *
  35. * @augments Highcharts.Series
  36. */
  37. seriesType('pie', 'line',
  38. /**
  39. * A pie chart is a circular graphic which is divided into slices to
  40. * illustrate numerical proportion.
  41. *
  42. * @sample highcharts/demo/pie-basic/
  43. * Pie chart
  44. *
  45. * @extends plotOptions.line
  46. * @excluding animationLimit, boostThreshold, connectEnds, connectNulls,
  47. * cropThreshold, dashStyle, findNearestPointBy,
  48. * getExtremesFromAll, lineWidth, marker, negativeColor,
  49. * pointInterval, pointIntervalUnit, pointPlacement,
  50. * pointStart, softThreshold, stacking, step, threshold,
  51. * turboThreshold, zoneAxis, zones
  52. * @product highcharts
  53. * @optionparent plotOptions.pie
  54. */
  55. {
  56. /**
  57. * @excluding legendItemClick
  58. * @apioption plotOptions.pie.events
  59. */
  60. /**
  61. * Fires when the checkbox next to the point name in the legend is
  62. * clicked. One parameter, event, is passed to the function. The state
  63. * of the checkbox is found by event.checked. The checked item is found
  64. * by event.item. Return false to prevent the default action which is to
  65. * toggle the select state of the series.
  66. *
  67. * @sample {highcharts} highcharts/plotoptions/series-events-checkboxclick/
  68. * Alert checkbox status
  69. *
  70. * @type {Function}
  71. * @since 1.2.0
  72. * @product highcharts
  73. * @context Highcharts.Point
  74. * @apioption plotOptions.pie.events.checkboxClick
  75. */
  76. /**
  77. * The center of the pie chart relative to the plot area. Can be
  78. * percentages or pixel values. The default behaviour (as of 3.0) is to
  79. * center the pie so that all slices and data labels are within the plot
  80. * area. As a consequence, the pie may actually jump around in a chart
  81. * with dynamic values, as the data labels move. In that case, the
  82. * center should be explicitly set, for example to `["50%", "50%"]`.
  83. *
  84. * @sample {highcharts} highcharts/plotoptions/pie-center/
  85. * Centered at 100, 100
  86. *
  87. * @type {Array<number|string|null>}
  88. * @default [null, null]
  89. * @product highcharts
  90. */
  91. center: [null, null],
  92. /**
  93. * @product highcharts
  94. */
  95. clip: false,
  96. /** @ignore */
  97. colorByPoint: true, // always true for pies
  98. /**
  99. * A series specific or series type specific color set to use instead
  100. * of the global [colors](#colors).
  101. *
  102. * @sample {highcharts} highcharts/demo/pie-monochrome/
  103. * Set default colors for all pies
  104. *
  105. * @type {Array<Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject>}
  106. * @since 3.0
  107. * @product highcharts
  108. * @apioption plotOptions.pie.colors
  109. */
  110. /**
  111. * @extends plotOptions.series.dataLabels
  112. * @excluding align, allowOverlap, staggerLines, step
  113. * @product highcharts
  114. */
  115. dataLabels: {
  116. allowOverlap: true,
  117. /**
  118. * The color of the line connecting the data label to the pie slice.
  119. * The default color is the same as the point's color.
  120. *
  121. * In styled mode, the connector stroke is given in the
  122. * `.highcharts-data-label-connector` class.
  123. *
  124. * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorcolor/
  125. * Blue connectors
  126. * @sample {highcharts} highcharts/css/pie-point/
  127. * Styled connectors
  128. *
  129. * @type {Highcharts.ColorString}
  130. * @since 2.1
  131. * @product highcharts
  132. * @apioption plotOptions.pie.dataLabels.connectorColor
  133. */
  134. /**
  135. * The distance from the data label to the connector. Note that data
  136. * labels also have a default `padding`, so in order for the
  137. * connector to touch the text, the `padding` must also be 0.
  138. *
  139. * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorpadding/
  140. * No padding
  141. *
  142. * @since 2.1
  143. * @product highcharts
  144. * @apioption plotOptions.pie.dataLabels.connectorPadding
  145. */
  146. connectorPadding: 5,
  147. /**
  148. * The width of the line connecting the data label to the pie slice.
  149. *
  150. *
  151. * In styled mode, the connector stroke width is given in the
  152. * `.highcharts-data-label-connector` class.
  153. *
  154. * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorwidth-disabled/
  155. * Disable the connector
  156. * @sample {highcharts} highcharts/css/pie-point/
  157. * Styled connectors
  158. *
  159. * @type {number}
  160. * @default 1
  161. * @since 2.1
  162. * @product highcharts
  163. * @apioption plotOptions.pie.dataLabels.connectorWidth
  164. */
  165. /**
  166. * @sample {highcharts} highcharts/plotOptions/pie-datalabels-overflow
  167. * Long labels truncated with an ellipsis
  168. * @sample {highcharts} highcharts/plotOptions/pie-datalabels-overflow-wrap
  169. * Long labels are wrapped
  170. *
  171. * @type {Highcharts.CSSObject}
  172. * @apioption plotOptions.pie.dataLabels.style
  173. */
  174. /**
  175. * The distance of the data label from the pie's edge. Negative
  176. * numbers put the data label on top of the pie slices. Connectors
  177. * are only shown for data labels outside the pie.
  178. *
  179. * @sample {highcharts} highcharts/plotoptions/pie-datalabels-distance/
  180. * Data labels on top of the pie
  181. *
  182. * @since 2.1
  183. * @product highcharts
  184. */
  185. distance: 30,
  186. /**
  187. * Enable or disable the data labels.
  188. *
  189. * @since 2.1
  190. * @product highcharts
  191. */
  192. enabled: true,
  193. /**
  194. * @type {Highcharts.FormatterCallbackFunction<Highcharts.SeriesDataLabelsFormatterContextObject>}
  195. * @default function () { return this.point.name; }
  196. * @apioption plotOptions.pie.dataLabels.formatter
  197. */
  198. formatter: function () { // #2945
  199. return this.point.isNull ? undefined : this.point.name;
  200. },
  201. /**
  202. * Whether to render the connector as a soft arc or a line with
  203. * sharp break. Works only if `connectorShape` equals to
  204. * `fixedOffset`.
  205. *
  206. * @sample {highcharts} highcharts/plotoptions/pie-datalabels-softconnector-true/
  207. * Soft
  208. * @sample {highcharts} highcharts/plotoptions/pie-datalabels-softconnector-false/
  209. * Non soft
  210. *
  211. * @type {number}
  212. * @since 2.1.7
  213. * @product highcharts
  214. * @apioption plotOptions.pie.dataLabels.softConnector
  215. */
  216. softConnector: true,
  217. /**
  218. * Alignment method for data labels. Possible values are:
  219. * `'toPlotEdges'` (each label touches the nearest vertical edge of
  220. * the plot area) or `'connectors'` (connectors have the same x
  221. * position and the widest label of each half (left & right) touches
  222. * the nearest vertical edge of the plot area).
  223. *
  224. * @type {String}
  225. * @sample {highcharts} highcharts/plotoptions/pie-datalabels-alignto-connectors/ alignTo: connectors
  226. * @sample {highcharts} highcharts/plotoptions/pie-datalabels-alignto-plotedges/ alignTo: plotEdges
  227. * @since 7.0.0
  228. * @default undefined
  229. * @product highcharts
  230. * @apioption plotOptions.pie.dataLabels.alignTo
  231. */
  232. x: 0,
  233. /**
  234. * Specifies the method that is used to generate the connector path.
  235. * Highcharts provides 3 built-in connector shapes: `'fixedOffset'`
  236. * (default), `'straight'` and `'crookedLine'`. Using
  237. * `'crookedLine'` has the most sense (in most of the cases) when
  238. * `'alignTo'` is set.
  239. *
  240. * Users can provide their own method by passing a function instead
  241. * of a String. 3 arguments are passed to the callback:
  242. *
  243. * - Object that holds the information about the coordinates of the
  244. * label (`x` & `y` properties) and how the label is located in
  245. * relation to the pie (`alignment` property). `alignment` can by
  246. * one of the following:
  247. * `'left'` (pie on the left side of the data label),
  248. * `'right'` (pie on the right side of the data label) or
  249. * `'center'` (data label overlaps the pie).
  250. * - Object that holds the information about the position of the
  251. * connector. Its `touchingSliceAt` porperty tells the position
  252. * of the place where the connector touches the slice.
  253. * - Data label options
  254. *
  255. * The function has to return an SVG path definition in array form
  256. * (see the example).
  257. *
  258. * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorshape-string/
  259. * connectorShape is a String
  260. * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorshape-function/
  261. * connectorShape is a function
  262. *
  263. * @type {string|Function}
  264. * @since 7.0.0
  265. * @product highcharts
  266. * @apioption plotOptions.pie.dataLabels.connectorShape
  267. */
  268. connectorShape: 'fixedOffset',
  269. /**
  270. * Works only if `connectorShape` is `'crookedLine'`. It defines how
  271. * far from the vertical plot edge the coonnector path should be
  272. * crooked.
  273. *
  274. * @sample {highcharts} highcharts/plotoptions/pie-datalabels-crookdistance/
  275. * crookDistance set to 90%
  276. *
  277. * @type {string}
  278. * @since 7.0.0
  279. * @product highcharts
  280. * @apioption plotOptions.pie.dataLabels.crookDistance
  281. */
  282. crookDistance: '70%'
  283. },
  284. /**
  285. * The end angle of the pie in degrees where 0 is top and 90 is right.
  286. * Defaults to `startAngle` plus 360.
  287. *
  288. * @sample {highcharts} highcharts/demo/pie-semi-circle/
  289. * Semi-circle donut
  290. *
  291. * @type {number}
  292. * @since 1.3.6
  293. * @product highcharts
  294. * @apioption plotOptions.pie.endAngle
  295. */
  296. /**
  297. * Equivalent to [chart.ignoreHiddenSeries](#chart.ignoreHiddenSeries),
  298. * this option tells whether the series shall be redrawn as if the
  299. * hidden point were `null`.
  300. *
  301. * The default value changed from `false` to `true` with Highcharts
  302. * 3.0.
  303. *
  304. * @sample {highcharts} highcharts/plotoptions/pie-ignorehiddenpoint/
  305. * True, the hiddden point is ignored
  306. *
  307. * @since 2.3.0
  308. * @product highcharts
  309. */
  310. ignoreHiddenPoint: true,
  311. /**
  312. * The size of the inner diameter for the pie. A size greater than 0
  313. * renders a donut chart. Can be a percentage or pixel value.
  314. * Percentages are relative to the pie size. Pixel values are given as
  315. * integers.
  316. *
  317. *
  318. * Note: in Highcharts < 4.1.2, the percentage was relative to the plot
  319. * area, not the pie size.
  320. *
  321. * @sample {highcharts} highcharts/plotoptions/pie-innersize-80px/
  322. * 80px inner size
  323. * @sample {highcharts} highcharts/plotoptions/pie-innersize-50percent/
  324. * 50% of the plot area
  325. * @sample {highcharts} highcharts/demo/3d-pie-donut/
  326. * 3D donut
  327. *
  328. * @type {number|string}
  329. * @default 0
  330. * @since 2.0
  331. * @product highcharts
  332. * @apioption plotOptions.pie.innerSize
  333. */
  334. /** @ignore-option */
  335. legendType: 'point',
  336. /** @ignore-option */
  337. marker: null, // point options are specified in the base options
  338. /**
  339. * The minimum size for a pie in response to auto margins. The pie will
  340. * try to shrink to make room for data labels in side the plot area,
  341. * but only to this size.
  342. *
  343. * @type {number}
  344. * @default 80
  345. * @since 3.0
  346. * @product highcharts
  347. * @apioption plotOptions.pie.minSize
  348. */
  349. /**
  350. * The diameter of the pie relative to the plot area. Can be a
  351. * percentage or pixel value. Pixel values are given as integers. The
  352. * default behaviour (as of 3.0) is to scale to the plot area and give
  353. * room for data labels within the plot area.
  354. * [slicedOffset](#plotOptions.pie.slicedOffset) is also included in the
  355. * default size calculation. As a consequence, the size of the pie may
  356. * vary when points are updated and data labels more around. In that
  357. * case it is best to set a fixed value, for example `"75%"`.
  358. *
  359. * @sample {highcharts} highcharts/plotoptions/pie-size/
  360. * Smaller pie
  361. *
  362. * @type {number|string|null}
  363. * @product highcharts
  364. */
  365. size: null,
  366. /**
  367. * Whether to display this particular series or series type in the
  368. * legend. Since 2.1, pies are not shown in the legend by default.
  369. *
  370. * @sample {highcharts} highcharts/plotoptions/series-showinlegend/
  371. * One series in the legend, one hidden
  372. *
  373. * @product highcharts
  374. */
  375. showInLegend: false,
  376. /**
  377. * If a point is sliced, moved out from the center, how many pixels
  378. * should it be moved?.
  379. *
  380. * @sample {highcharts} highcharts/plotoptions/pie-slicedoffset-20/
  381. * 20px offset
  382. *
  383. * @product highcharts
  384. */
  385. slicedOffset: 10,
  386. /**
  387. * The start angle of the pie slices in degrees where 0 is top and 90
  388. * right.
  389. *
  390. * @sample {highcharts} highcharts/plotoptions/pie-startangle-90/
  391. * Start from right
  392. *
  393. * @type {number}
  394. * @default 0
  395. * @since 2.3.4
  396. * @product highcharts
  397. * @apioption plotOptions.pie.startAngle
  398. */
  399. /**
  400. * Sticky tracking of mouse events. When true, the `mouseOut` event
  401. * on a series isn't triggered until the mouse moves over another
  402. * series, or out of the plot area. When false, the `mouseOut` event on
  403. * a series is triggered when the mouse leaves the area around the
  404. * series' graph or markers. This also implies the tooltip. When
  405. * `stickyTracking` is false and `tooltip.shared` is false, the tooltip
  406. * will be hidden when moving the mouse between series.
  407. *
  408. * @product highcharts
  409. */
  410. stickyTracking: false,
  411. tooltip: {
  412. followPointer: true
  413. },
  414. /**
  415. * The color of the border surrounding each slice. When `null`, the
  416. * border takes the same color as the slice fill. This can be used
  417. * together with a `borderWidth` to fill drawing gaps created by
  418. * antialiazing artefacts in borderless pies.
  419. *
  420. * In styled mode, the border stroke is given in the `.highcharts-point`
  421. * class.
  422. *
  423. * @sample {highcharts} highcharts/plotoptions/pie-bordercolor-black/
  424. * Black border
  425. *
  426. * @type {Highcharts.ColorString}
  427. * @default #ffffff
  428. * @product highcharts
  429. */
  430. borderColor: '#ffffff',
  431. /**
  432. * The width of the border surrounding each slice.
  433. *
  434. * When setting the border width to 0, there may be small gaps between
  435. * the slices due to SVG antialiasing artefacts. To work around this,
  436. * keep the border width at 0.5 or 1, but set the `borderColor` to
  437. * `null` instead.
  438. *
  439. * In styled mode, the border stroke width is given in the
  440. * `.highcharts-point` class.
  441. *
  442. * @sample {highcharts} highcharts/plotoptions/pie-borderwidth/
  443. * 3px border
  444. *
  445. * @product highcharts
  446. */
  447. borderWidth: 1,
  448. states: {
  449. /**
  450. * @extends plotOptions.series.states.hover
  451. * @excluding marker, lineWidth, lineWidthPlus
  452. * @product highcharts
  453. */
  454. hover: {
  455. /**
  456. * How much to brighten the point on interaction. Requires the main
  457. * color to be defined in hex or rgb(a) format.
  458. *
  459. * In styled mode, the hover brightness is by default replaced
  460. * by a fill-opacity given in the `.highcharts-point-hover` class.
  461. *
  462. * @sample {highcharts} highcharts/plotoptions/pie-states-hover-brightness/
  463. * Brightened by 0.5
  464. *
  465. * @product highcharts
  466. */
  467. brightness: 0.1
  468. }
  469. }
  470. }, /** @lends seriesTypes.pie.prototype */ {
  471. isCartesian: false,
  472. requireSorting: false,
  473. directTouch: true,
  474. noSharedTooltip: true,
  475. trackerGroups: ['group', 'dataLabelsGroup'],
  476. axisTypes: [],
  477. pointAttribs: seriesTypes.column.prototype.pointAttribs,
  478. /**
  479. * Animate the pies in
  480. *
  481. * @private
  482. * @function Highcharts.seriesTypes.pie#animate
  483. *
  484. * @param {boolean} [init=false]
  485. */
  486. animate: function (init) {
  487. var series = this,
  488. points = series.points,
  489. startAngleRad = series.startAngleRad;
  490. if (!init) {
  491. points.forEach(function (point) {
  492. var graphic = point.graphic,
  493. args = point.shapeArgs;
  494. if (graphic) {
  495. // start values
  496. graphic.attr({
  497. // animate from inner radius (#779)
  498. r: point.startR || (series.center[3] / 2),
  499. start: startAngleRad,
  500. end: startAngleRad
  501. });
  502. // animate
  503. graphic.animate({
  504. r: args.r,
  505. start: args.start,
  506. end: args.end
  507. }, series.options.animation);
  508. }
  509. });
  510. // delete this function to allow it only once
  511. series.animate = null;
  512. }
  513. },
  514. /**
  515. * Recompute total chart sum and update percentages of points.
  516. *
  517. * @private
  518. * @function Highcharts.seriesTypes.pie#updateTotals
  519. */
  520. updateTotals: function () {
  521. var i,
  522. total = 0,
  523. points = this.points,
  524. len = points.length,
  525. point,
  526. ignoreHiddenPoint = this.options.ignoreHiddenPoint;
  527. // Get the total sum
  528. for (i = 0; i < len; i++) {
  529. point = points[i];
  530. total += (ignoreHiddenPoint && !point.visible) ?
  531. 0 :
  532. point.isNull ? 0 : point.y;
  533. }
  534. this.total = total;
  535. // Set each point's properties
  536. for (i = 0; i < len; i++) {
  537. point = points[i];
  538. point.percentage =
  539. (total > 0 && (point.visible || !ignoreHiddenPoint)) ?
  540. point.y / total * 100 :
  541. 0;
  542. point.total = total;
  543. }
  544. },
  545. /**
  546. * Extend the generatePoints method by adding total and percentage
  547. * properties to each point
  548. *
  549. * @private
  550. * @function Highcharts.seriesTypes.pie#generatePoints
  551. */
  552. generatePoints: function () {
  553. Series.prototype.generatePoints.call(this);
  554. this.updateTotals();
  555. },
  556. // Utility for getting the x value from a given y, used for
  557. // anticollision logic in data labels. Added point for using specific
  558. // points' label distance.
  559. getX: function (y, left, point) {
  560. var center = this.center,
  561. // Variable pie has individual radius
  562. radius = this.radii ? this.radii[point.index] : center[2] / 2,
  563. angle,
  564. x;
  565. angle = Math.asin(
  566. Math.max(
  567. Math.min(
  568. (
  569. (y - center[1]) /
  570. (radius + point.labelDistance)
  571. ),
  572. 1
  573. ),
  574. -1
  575. )
  576. );
  577. x = center[0] +
  578. (left ? -1 : 1) *
  579. (Math.cos(angle) * (radius + point.labelDistance)) +
  580. (
  581. point.labelDistance > 0 ?
  582. (left ? -1 : 1) * this.options.dataLabels.padding :
  583. 0
  584. );
  585. return x;
  586. },
  587. /**
  588. * Do translation for pie slices
  589. *
  590. * @private
  591. * @function Highcharts.seriesTypes.pie#translate
  592. *
  593. * @param {Array<number>} positions
  594. */
  595. translate: function (positions) {
  596. this.generatePoints();
  597. var series = this,
  598. cumulative = 0,
  599. precision = 1000, // issue #172
  600. options = series.options,
  601. slicedOffset = options.slicedOffset,
  602. connectorOffset = slicedOffset + (options.borderWidth || 0),
  603. finalConnectorOffset,
  604. start,
  605. end,
  606. angle,
  607. radians = getStartAndEndRadians(
  608. options.startAngle,
  609. options.endAngle
  610. ),
  611. startAngleRad = series.startAngleRad = radians.start,
  612. endAngleRad = series.endAngleRad = radians.end,
  613. circ = endAngleRad - startAngleRad, // 2 * Math.PI,
  614. points = series.points,
  615. // the x component of the radius vector for a given point
  616. radiusX,
  617. radiusY,
  618. labelDistance = options.dataLabels.distance,
  619. ignoreHiddenPoint = options.ignoreHiddenPoint,
  620. i,
  621. len = points.length,
  622. point;
  623. // Get positions - either an integer or a percentage string must be
  624. // given. If positions are passed as a parameter, we're in a
  625. // recursive loop for adjusting space for data labels.
  626. if (!positions) {
  627. series.center = positions = series.getCenter();
  628. }
  629. // Calculate the geometry for each point
  630. for (i = 0; i < len; i++) {
  631. point = points[i];
  632. // Used for distance calculation for specific point.
  633. point.labelDistance = pick(
  634. (
  635. point.options.dataLabels &&
  636. point.options.dataLabels.distance
  637. ),
  638. labelDistance
  639. );
  640. // Saved for later dataLabels distance calculation.
  641. series.maxLabelDistance = Math.max(
  642. series.maxLabelDistance || 0,
  643. point.labelDistance
  644. );
  645. // set start and end angle
  646. start = startAngleRad + (cumulative * circ);
  647. if (!ignoreHiddenPoint || point.visible) {
  648. cumulative += point.percentage / 100;
  649. }
  650. end = startAngleRad + (cumulative * circ);
  651. // set the shape
  652. point.shapeType = 'arc';
  653. point.shapeArgs = {
  654. x: positions[0],
  655. y: positions[1],
  656. r: positions[2] / 2,
  657. innerR: positions[3] / 2,
  658. start: Math.round(start * precision) / precision,
  659. end: Math.round(end * precision) / precision
  660. };
  661. // The angle must stay within -90 and 270 (#2645)
  662. angle = (end + start) / 2;
  663. if (angle > 1.5 * Math.PI) {
  664. angle -= 2 * Math.PI;
  665. } else if (angle < -Math.PI / 2) {
  666. angle += 2 * Math.PI;
  667. }
  668. // Center for the sliced out slice
  669. point.slicedTranslation = {
  670. translateX: Math.round(Math.cos(angle) * slicedOffset),
  671. translateY: Math.round(Math.sin(angle) * slicedOffset)
  672. };
  673. // set the anchor point for tooltips
  674. radiusX = Math.cos(angle) * positions[2] / 2;
  675. radiusY = Math.sin(angle) * positions[2] / 2;
  676. point.tooltipPos = [
  677. positions[0] + radiusX * 0.7,
  678. positions[1] + radiusY * 0.7
  679. ];
  680. point.half = angle < -Math.PI / 2 || angle > Math.PI / 2 ?
  681. 1 :
  682. 0;
  683. point.angle = angle;
  684. // Set the anchor point for data labels. Use point.labelDistance
  685. // instead of labelDistance // #1174
  686. // finalConnectorOffset - not override connectorOffset value.
  687. finalConnectorOffset = Math.min(
  688. connectorOffset,
  689. point.labelDistance / 5
  690. ); // #1678
  691. point.labelPosition = {
  692. natural: {
  693. // initial position of the data label - it's utilized for
  694. // finding the final position for the label
  695. x: positions[0] + radiusX + Math.cos(angle) *
  696. point.labelDistance,
  697. y: positions[1] + radiusY + Math.sin(angle) *
  698. point.labelDistance
  699. },
  700. 'final': {
  701. // used for generating connector path -
  702. // initialized later in drawDataLabels function
  703. // x: undefined,
  704. // y: undefined
  705. },
  706. // left - pie on the left side of the data label
  707. // right - pie on the right side of the data label
  708. // center - data label overlaps the pie
  709. alignment: point.labelDistance < 0 ?
  710. 'center' : point.half ? 'right' : 'left',
  711. connectorPosition: {
  712. breakAt: { // used in connectorShapes.fixedOffset
  713. x: positions[0] + radiusX + Math.cos(angle) *
  714. finalConnectorOffset,
  715. y: positions[1] + radiusY + Math.sin(angle) *
  716. finalConnectorOffset
  717. },
  718. touchingSliceAt: { // middle of the arc
  719. x: positions[0] + radiusX,
  720. y: positions[1] + radiusY
  721. }
  722. }
  723. };
  724. }
  725. },
  726. /**
  727. * @private
  728. * @deprecated
  729. * @name Highcharts.seriesTypes.pie#drawGraph
  730. * @type {null}
  731. */
  732. drawGraph: null,
  733. /**
  734. * Draw the data points
  735. *
  736. * @private
  737. * @function Highcharts.seriesTypes.pie#drawPoints
  738. */
  739. drawPoints: function () {
  740. var series = this,
  741. chart = series.chart,
  742. renderer = chart.renderer,
  743. groupTranslation,
  744. graphic,
  745. pointAttr,
  746. shapeArgs,
  747. shadow = series.options.shadow;
  748. if (shadow && !series.shadowGroup && !chart.styledMode) {
  749. series.shadowGroup = renderer.g('shadow')
  750. .add(series.group);
  751. }
  752. // draw the slices
  753. series.points.forEach(function (point) {
  754. graphic = point.graphic;
  755. if (!point.isNull) {
  756. shapeArgs = point.shapeArgs;
  757. // If the point is sliced, use special translation, else use
  758. // plot area traslation
  759. groupTranslation = point.getTranslate();
  760. if (!chart.styledMode) {
  761. // Put the shadow behind all points
  762. var shadowGroup = point.shadowGroup;
  763. if (shadow && !shadowGroup) {
  764. shadowGroup = point.shadowGroup = renderer
  765. .g('shadow')
  766. .add(series.shadowGroup);
  767. }
  768. if (shadowGroup) {
  769. shadowGroup.attr(groupTranslation);
  770. }
  771. pointAttr = series.pointAttribs(
  772. point,
  773. point.selected && 'select'
  774. );
  775. }
  776. // Draw the slice
  777. if (graphic) {
  778. graphic
  779. .setRadialReference(series.center);
  780. if (!chart.styledMode) {
  781. graphic.attr(pointAttr);
  782. }
  783. graphic.animate(extend(shapeArgs, groupTranslation));
  784. } else {
  785. point.graphic = graphic = renderer[point.shapeType](
  786. shapeArgs
  787. )
  788. .setRadialReference(series.center)
  789. .attr(groupTranslation)
  790. .add(series.group);
  791. if (!chart.styledMode) {
  792. graphic
  793. .attr(pointAttr)
  794. .attr({ 'stroke-linejoin': 'round' })
  795. .shadow(shadow, shadowGroup);
  796. }
  797. }
  798. graphic.attr({
  799. visibility: point.visible ? 'inherit' : 'hidden'
  800. });
  801. graphic.addClass(point.getClassName());
  802. } else if (graphic) {
  803. point.graphic = graphic.destroy();
  804. }
  805. });
  806. },
  807. /**
  808. * @private
  809. * @deprecated
  810. * @function Highcharts.seriesTypes.pie#searchPoint
  811. */
  812. searchPoint: noop,
  813. /**
  814. * Utility for sorting data labels
  815. *
  816. * @private
  817. * @function Highcharts.seriesTypes.pie#sortByAngle
  818. *
  819. * @param {Array<Highcharts.Point>} points
  820. *
  821. * @param {number} sign
  822. */
  823. sortByAngle: function (points, sign) {
  824. points.sort(function (a, b) {
  825. return a.angle !== undefined && (b.angle - a.angle) * sign;
  826. });
  827. },
  828. /**
  829. * Use a simple symbol from LegendSymbolMixin.
  830. *
  831. * @private
  832. * @borrows Highcharts.LegendSymbolMixin.drawRectangle as Highcharts.seriesTypes.pie#drawLegendSymbol
  833. */
  834. drawLegendSymbol: LegendSymbolMixin.drawRectangle,
  835. /**
  836. * Use the getCenter method from drawLegendSymbol.
  837. *
  838. * @private
  839. * @borrows Highcharts.CenteredSeriesMixin.getCenter as Highcharts.seriesTypes.pie#getCenter
  840. */
  841. getCenter: CenteredSeriesMixin.getCenter,
  842. /**
  843. * Pies don't have point marker symbols.
  844. *
  845. * @deprecated
  846. * @private
  847. * @function Highcharts.seriesTypes.pie#getSymbol
  848. */
  849. getSymbol: noop
  850. }, /** @lends seriesTypes.pie.prototype.pointClass.prototype */ {
  851. /**
  852. * Initiate the pie slice
  853. *
  854. * @private
  855. * @function Highcharts.seriesTypes.pie#pointClass#init
  856. *
  857. * @return {Highcharts.Point}
  858. */
  859. init: function () {
  860. Point.prototype.init.apply(this, arguments);
  861. var point = this,
  862. toggleSlice;
  863. point.name = pick(point.name, 'Slice');
  864. // add event listener for select
  865. toggleSlice = function (e) {
  866. point.slice(e.type === 'select');
  867. };
  868. addEvent(point, 'select', toggleSlice);
  869. addEvent(point, 'unselect', toggleSlice);
  870. return point;
  871. },
  872. /**
  873. * Negative points are not valid (#1530, #3623, #5322)
  874. *
  875. * @private
  876. * @function Highcharts.seriesTypes.pie#pointClass#isValid
  877. *
  878. * @return {boolean}
  879. */
  880. isValid: function () {
  881. return H.isNumber(this.y, true) && this.y >= 0;
  882. },
  883. /**
  884. * Toggle the visibility of the pie slice
  885. *
  886. * @private
  887. * @function Highcharts.seriesTypes.pie#pointClass#setVisible
  888. *
  889. * @param {boolean} vis
  890. * Whether to show the slice or not. If undefined, the visibility is
  891. * toggled.
  892. *
  893. * @param {boolean} [redraw=false]
  894. */
  895. setVisible: function (vis, redraw) {
  896. var point = this,
  897. series = point.series,
  898. chart = series.chart,
  899. ignoreHiddenPoint = series.options.ignoreHiddenPoint;
  900. redraw = pick(redraw, ignoreHiddenPoint);
  901. if (vis !== point.visible) {
  902. // If called without an argument, toggle visibility
  903. point.visible = point.options.visible = vis =
  904. vis === undefined ? !point.visible : vis;
  905. // update userOptions.data
  906. series.options.data[series.data.indexOf(point)] = point.options;
  907. // Show and hide associated elements. This is performed
  908. // regardless of redraw or not, because chart.redraw only
  909. // handles full series.
  910. ['graphic', 'dataLabel', 'connector', 'shadowGroup'].forEach(
  911. function (key) {
  912. if (point[key]) {
  913. point[key][vis ? 'show' : 'hide'](true);
  914. }
  915. }
  916. );
  917. if (point.legendItem) {
  918. chart.legend.colorizeItem(point, vis);
  919. }
  920. // #4170, hide halo after hiding point
  921. if (!vis && point.state === 'hover') {
  922. point.setState('');
  923. }
  924. // Handle ignore hidden slices
  925. if (ignoreHiddenPoint) {
  926. series.isDirty = true;
  927. }
  928. if (redraw) {
  929. chart.redraw();
  930. }
  931. }
  932. },
  933. /**
  934. * Set or toggle whether the slice is cut out from the pie
  935. *
  936. * @private
  937. * @function Highcharts.seriesTypes.pie#pointClass#slice
  938. *
  939. * @param {boolean} sliced
  940. * When undefined, the slice state is toggled.
  941. *
  942. * @param {boolean} redraw
  943. * Whether to redraw the chart. True by default.
  944. */
  945. slice: function (sliced, redraw, animation) {
  946. var point = this,
  947. series = point.series,
  948. chart = series.chart;
  949. setAnimation(animation, chart);
  950. // redraw is true by default
  951. redraw = pick(redraw, true);
  952. // if called without an argument, toggle
  953. point.sliced = point.options.sliced = sliced =
  954. defined(sliced) ? sliced : !point.sliced;
  955. // update userOptions.data
  956. series.options.data[series.data.indexOf(point)] = point.options;
  957. point.graphic.animate(this.getTranslate());
  958. if (point.shadowGroup) {
  959. point.shadowGroup.animate(this.getTranslate());
  960. }
  961. },
  962. /**
  963. * @private
  964. * @function Highcharts.seriesTypes.pie#pointClass#getTranslate
  965. *
  966. * @return {*}
  967. */
  968. getTranslate: function () {
  969. return this.sliced ? this.slicedTranslation : {
  970. translateX: 0,
  971. translateY: 0
  972. };
  973. },
  974. /**
  975. * @private
  976. * @function Highcharts.seriesTypes.pie#pointClass#haloPath
  977. *
  978. * @param {number} size
  979. *
  980. * @return {Highcharts.SVGPathArray}
  981. */
  982. haloPath: function (size) {
  983. var shapeArgs = this.shapeArgs;
  984. return this.sliced || !this.visible ?
  985. [] :
  986. this.series.chart.renderer.symbols.arc(
  987. shapeArgs.x,
  988. shapeArgs.y,
  989. shapeArgs.r + size,
  990. shapeArgs.r + size, {
  991. // Substract 1px to ensure the background is not bleeding
  992. // through between the halo and the slice (#7495).
  993. innerR: this.shapeArgs.r - 1,
  994. start: shapeArgs.start,
  995. end: shapeArgs.end
  996. }
  997. );
  998. },
  999. connectorShapes: {
  1000. // only one available before v7.0.0
  1001. fixedOffset: function (labelPosition, connectorPosition, options) {
  1002. var breakAt = connectorPosition.breakAt,
  1003. touchingSliceAt = connectorPosition.touchingSliceAt,
  1004. linePath = options.softConnector ? [
  1005. 'C', // soft break
  1006. // 1st control point (of the curve)
  1007. labelPosition.x +
  1008. // 5 gives the connector a little horizontal bend
  1009. (labelPosition.alignment === 'left' ? -5 : 5),
  1010. labelPosition.y, //
  1011. 2 * breakAt.x - touchingSliceAt.x, // 2nd control point
  1012. 2 * breakAt.y - touchingSliceAt.y, //
  1013. breakAt.x, // end of the curve
  1014. breakAt.y //
  1015. ] : [
  1016. 'L', // pointy break
  1017. breakAt.x,
  1018. breakAt.y
  1019. ];
  1020. // assemble the path
  1021. return [
  1022. 'M',
  1023. labelPosition.x,
  1024. labelPosition.y
  1025. ].concat(linePath).concat([
  1026. 'L',
  1027. touchingSliceAt.x,
  1028. touchingSliceAt.y
  1029. ]);
  1030. },
  1031. straight: function (labelPosition, connectorPosition) {
  1032. var touchingSliceAt = connectorPosition.touchingSliceAt;
  1033. // direct line to the slice
  1034. return [
  1035. 'M',
  1036. labelPosition.x,
  1037. labelPosition.y,
  1038. 'L',
  1039. touchingSliceAt.x,
  1040. touchingSliceAt.y
  1041. ];
  1042. },
  1043. crookedLine: function (labelPosition, connectorPosition,
  1044. options) {
  1045. var touchingSliceAt = connectorPosition.touchingSliceAt,
  1046. series = this.series,
  1047. pieCenterX = series.center[0],
  1048. plotWidth = series.chart.plotWidth,
  1049. plotLeft = series.chart.plotLeft,
  1050. alignment = labelPosition.alignment,
  1051. radius = this.shapeArgs.r,
  1052. crookDistance =
  1053. H.relativeLength(options.crookDistance, 1), // % to fraction
  1054. crookX = alignment === 'left' ?
  1055. pieCenterX + radius + (plotWidth + plotLeft -
  1056. pieCenterX - radius) * (1 - crookDistance) :
  1057. plotLeft + (pieCenterX - radius) * crookDistance,
  1058. segmentWithCrook = ['L',
  1059. crookX,
  1060. labelPosition.y];
  1061. // crookedLine formula doesn't make sense if the path overlaps
  1062. // the label - use straight line instead in that case
  1063. if (alignment === 'left' ?
  1064. (crookX > labelPosition.x || crookX < touchingSliceAt.x) :
  1065. (crookX < labelPosition.x || crookX > touchingSliceAt.x)) {
  1066. segmentWithCrook = []; // remove the crook
  1067. }
  1068. // assemble the path
  1069. return [
  1070. 'M',
  1071. labelPosition.x,
  1072. labelPosition.y
  1073. ].concat(segmentWithCrook).concat(['L',
  1074. touchingSliceAt.x,
  1075. touchingSliceAt.y
  1076. ]);
  1077. }
  1078. },
  1079. /**
  1080. * Extendable method for getting the path of the connector between the data
  1081. * label and the pie slice.
  1082. */
  1083. getConnectorPath: function () {
  1084. var labelPosition = this.labelPosition,
  1085. options = this.series.options.dataLabels,
  1086. connectorShape = options.connectorShape,
  1087. predefinedShapes = this.connectorShapes;
  1088. // find out whether to use the predefined shape
  1089. if (predefinedShapes[connectorShape]) {
  1090. connectorShape = predefinedShapes[connectorShape];
  1091. }
  1092. return connectorShape.call(this, {
  1093. // pass simplified label position object for user's convenience
  1094. x: labelPosition.final.x,
  1095. y: labelPosition.final.y,
  1096. alignment: labelPosition.alignment
  1097. }, labelPosition.connectorPosition, options);
  1098. }
  1099. });
  1100. /**
  1101. * A `pie` series. If the [type](#series.pie.type) option is not specified,
  1102. * it is inherited from [chart.type](#chart.type).
  1103. *
  1104. * @extends series,plotOptions.pie
  1105. * @excluding dataParser, dataURL, stack, xAxis, yAxis
  1106. * @product highcharts
  1107. * @apioption series.pie
  1108. */
  1109. /**
  1110. * An array of data points for the series. For the `pie` series type,
  1111. * points can be given in the following ways:
  1112. *
  1113. * 1. An array of numerical values. In this case, the numerical values
  1114. * will be interpreted as `y` options. Example:
  1115. *
  1116. * ```js
  1117. * data: [0, 5, 3, 5]
  1118. * ```
  1119. *
  1120. * 2. An array of objects with named values. The following snippet shows only a
  1121. * few settings, see the complete options set below. If the total number of data
  1122. * points exceeds the series' [turboThreshold](#series.pie.turboThreshold),
  1123. * this option is not available.
  1124. *
  1125. * ```js
  1126. * data: [{
  1127. * y: 1,
  1128. * name: "Point2",
  1129. * color: "#00FF00"
  1130. * }, {
  1131. * y: 7,
  1132. * name: "Point1",
  1133. * color: "#FF00FF"
  1134. * }]</pre>
  1135. *
  1136. * @sample {highcharts} highcharts/chart/reflow-true/
  1137. * Numerical values
  1138. * @sample {highcharts} highcharts/series/data-array-of-arrays/
  1139. * Arrays of numeric x and y
  1140. * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
  1141. * Arrays of datetime x and y
  1142. * @sample {highcharts} highcharts/series/data-array-of-name-value/
  1143. * Arrays of point.name and y
  1144. * @sample {highcharts} highcharts/series/data-array-of-objects/
  1145. * Config objects
  1146. *
  1147. * @type {Array<number|Array<string,number>|*>}
  1148. * @extends series.line.data
  1149. * @excluding marker, x
  1150. * @product highcharts
  1151. * @apioption series.pie.data
  1152. */
  1153. /**
  1154. * The sequential index of the data point in the legend.
  1155. *
  1156. * @type {number}
  1157. * @product highcharts
  1158. * @apioption series.pie.data.legendIndex
  1159. */
  1160. /**
  1161. * Fires when the legend item belonging to the pie point (slice) is
  1162. * clicked. The `this` keyword refers to the point itself. One parameter,
  1163. * `event`, is passed to the function, containing common event information. The
  1164. * default action is to toggle the visibility of the point. This can be
  1165. * prevented by calling `event.preventDefault()`.
  1166. *
  1167. * @sample {highcharts} highcharts/plotoptions/pie-point-events-legenditemclick/
  1168. * Confirm toggle visibility
  1169. *
  1170. * @type {Function}
  1171. * @since 1.2.0
  1172. * @product highcharts
  1173. * @apioption plotOptions.pie.point.events.legendItemClick
  1174. */
  1175. /**
  1176. * Whether to display a slice offset from the center.
  1177. *
  1178. * @sample {highcharts} highcharts/point/sliced/
  1179. * One sliced point
  1180. *
  1181. * @type {boolean}
  1182. * @product highcharts
  1183. * @apioption series.pie.data.sliced
  1184. */
  1185. /**
  1186. * @excluding legendItemClick
  1187. * @apioption series.pie.events
  1188. */