text: /* *
   *
   *  This module implements sunburst charts in Highcharts.
   *
   *  (c) 2016-2019 Highsoft AS
   *
   *  Authors: Jon Arild Nygard
   *
   *  License: www.highcharts.com/license
   *
   * */
  
  /**
   * Possible rotation options for data labels in the sunburst series.
   *
   * @typedef {"auto"|"perpendicular"|"parallel"} Highcharts.SeriesSunburstDataLabelsRotationValue
   */
  
  /**
   * Options for data labels in the sunburst series.
   *
   * @interface Highcharts.SeriesSunburstDataLabelsOptionsObject
   * @extends Highcharts.DataLabelsOptionsObject
   *//**
   * @name Highcharts.SeriesSunburstDataLabelsOptionsObject#align
   * @type {undefined}
   *//**
   * @name Highcharts.SeriesSunburstDataLabelsOptionsObject#allowOverlap
   * @type {undefined}
   *//**
   * Decides how the data label will be rotated relative to the perimeter
   * of the sunburst. Valid values are `auto`, `parallel` and
   * `perpendicular`. When `auto`, the best fit will be computed for the
   * point.
   *
   * The `series.rotation` option takes precedence over `rotationMode`.
   *
   * @name Highcharts.SeriesSunburstDataLabelsOptionsObject#rotationMode
   * @type {Highcharts.SeriesSunburstDataLabelsRotationValue|undefined}
   * @since 6.0.0
   */
  
  'use strict';
  
  import H from '../parts/Globals.js';
  
  import U from '../parts/Utilities.js';
  var isNumber = U.isNumber,
      isObject = U.isObject,
      isString = U.isString;
  
  import '../mixins/centered-series.js';
  import drawPoint from '../mixins/draw-point.js';
  import mixinTreeSeries from '../mixins/tree-series.js';
  import '../parts/Series.js';
  import './treemap.src.js';
  
  var CenteredSeriesMixin = H.CenteredSeriesMixin,
      Series = H.Series,
      extend = H.extend,
      getCenter = CenteredSeriesMixin.getCenter,
      getColor = mixinTreeSeries.getColor,
      getLevelOptions = mixinTreeSeries.getLevelOptions,
      getStartAndEndRadians = CenteredSeriesMixin.getStartAndEndRadians,
      isBoolean = function (x) {
          return typeof x === 'boolean';
      },
      merge = H.merge,
      noop = H.noop,
      rad2deg = 180 / Math.PI,
      seriesType = H.seriesType,
      seriesTypes = H.seriesTypes,
      setTreeValues = mixinTreeSeries.setTreeValues,
      updateRootId = mixinTreeSeries.updateRootId;
  
  // TODO introduce step, which should default to 1.
  var range = function range(from, to) {
      var result = [],
          i;
  
      if (isNumber(from) && isNumber(to) && from <= to) {
          for (i = from; i <= to; i++) {
              result.push(i);
          }
      }
      return result;
  };
  
  /**
   * @private
   * @function calculateLevelSizes
   *
   * @param {object} levelOptions
   *        Map of level to its options.
   *
   * @param {object} params
   *        Object containing number parameters `innerRadius` and `outerRadius`.
   */
  var calculateLevelSizes = function calculateLevelSizes(levelOptions, params) {
      var result,
          p = isObject(params) ? params : {},
          totalWeight = 0,
          diffRadius,
          levels,
          levelsNotIncluded,
          remainingSize,
          from,
          to;
  
      if (isObject(levelOptions)) {
          result = merge({}, levelOptions); // Copy levelOptions
          from = isNumber(p.from) ? p.from : 0;
          to = isNumber(p.to) ? p.to : 0;
          levels = range(from, to);
          levelsNotIncluded = Object.keys(result).filter(function (k) {
              return levels.indexOf(+k) === -1;
          });
          diffRadius = remainingSize = isNumber(p.diffRadius) ? p.diffRadius : 0;
  
          // Convert percentage to pixels.
          // Calculate the remaining size to divide between "weight" levels.
          // Calculate total weight to use in convertion from weight to pixels.
          levels.forEach(function (level) {
              var options = result[level],
                  unit = options.levelSize.unit,
                  value = options.levelSize.value;
  
              if (unit === 'weight') {
                  totalWeight += value;
              } else if (unit === 'percentage') {
                  options.levelSize = {
                      unit: 'pixels',
                      value: (value / 100) * diffRadius
                  };
                  remainingSize -= options.levelSize.value;
              } else if (unit === 'pixels') {
                  remainingSize -= value;
              }
          });
  
          // Convert weight to pixels.
          levels.forEach(function (level) {
              var options = result[level],
                  weight;
  
              if (options.levelSize.unit === 'weight') {
                  weight = options.levelSize.value;
                  result[level].levelSize = {
                      unit: 'pixels',
                      value: (weight / totalWeight) * remainingSize
                  };
              }
          });
  
          // Set all levels not included in interval [from,to] to have 0 pixels.
          levelsNotIncluded.forEach(function (level) {
              result[level].levelSize = {
                  value: 0,
                  unit: 'pixels'
              };
          });
      }
      return result;
  };
  
  /**
   * Find a set of coordinates given a start coordinates, an angle, and a
   * distance.
   *
   * @private
   * @function getEndPoint
   *
   * @param {number} x
   *        Start coordinate x
   *
   * @param {number} y
   *        Start coordinate y
   *
   * @param {number} angle
   *        Angle in radians
   *
   * @param {number} distance
   *        Distance from start to end coordinates
   *
   * @return {Highcharts.SVGAttributes}
   *         Returns the end coordinates, x and y.
   */
  var getEndPoint = function getEndPoint(x, y, angle, distance) {
      return {
          x: x + (Math.cos(angle) * distance),
          y: y + (Math.sin(angle) * distance)
      };
  };
  
  var layoutAlgorithm = function layoutAlgorithm(parent, children, options) {
      var startAngle = parent.start,
          range = parent.end - startAngle,
          total = parent.val,
          x = parent.x,
          y = parent.y,
          radius = (
              (
                  options &&
                  isObject(options.levelSize) &&
                  isNumber(options.levelSize.value)
              ) ?
                  options.levelSize.value :
                  0
          ),
          innerRadius = parent.r,
          outerRadius = innerRadius + radius,
          slicedOffset = options && isNumber(options.slicedOffset) ?
              options.slicedOffset :
              0;
  
      return (children || []).reduce(function (arr, child) {
          var percentage = (1 / total) * child.val,
              radians = percentage * range,
              radiansCenter = startAngle + (radians / 2),
              offsetPosition = getEndPoint(x, y, radiansCenter, slicedOffset),
              values = {
                  x: child.sliced ? offsetPosition.x : x,
                  y: child.sliced ? offsetPosition.y : y,
                  innerR: innerRadius,
                  r: outerRadius,
                  radius: radius,
                  start: startAngle,
                  end: startAngle + radians
              };
  
          arr.push(values);
          startAngle = values.end;
          return arr;
      }, []);
  };
  
  var getDlOptions = function getDlOptions(params) {
      // Set options to new object to avoid problems with scope
      var point = params.point,
          shape = isObject(params.shapeArgs) ? params.shapeArgs : {},
          optionsPoint = (
              isObject(params.optionsPoint) ?
                  params.optionsPoint.dataLabels :
                  {}
          ),
          optionsLevel = (
              isObject(params.level) ?
                  params.level.dataLabels :
                  {}
          ),
          options = merge({
              style: {}
          }, optionsLevel, optionsPoint),
          rotationRad,
          rotation,
          rotationMode = options.rotationMode;
  
      if (!isNumber(options.rotation)) {
          if (rotationMode === 'auto') {
              if (
                  point.innerArcLength < 1 &&
                  point.outerArcLength > shape.radius
              ) {
                  rotationRad = 0;
              } else if (
                  point.innerArcLength > 1 &&
                  point.outerArcLength > 1.5 * shape.radius
              ) {
                  rotationMode = 'parallel';
              } else {
                  rotationMode = 'perpendicular';
              }
          }
  
          if (rotationMode !== 'auto') {
              rotationRad = (shape.end - (shape.end - shape.start) / 2);
          }
  
          if (rotationMode === 'parallel') {
              options.style.width = Math.min(
                  shape.radius * 2.5,
                  (point.outerArcLength + point.innerArcLength) / 2
              );
          } else {
              options.style.width = shape.radius;
          }
  
          if (
              rotationMode === 'perpendicular' &&
              point.series.chart.renderer.fontMetrics(options.style.fontSize).h >
              point.outerArcLength
          ) {
              options.style.width = 1;
          }
  
          // Apply padding (#8515)
          options.style.width = Math.max(
              options.style.width - 2 * (options.padding || 0),
              1
          );
  
          rotation = (rotationRad * rad2deg) % 180;
          if (rotationMode === 'parallel') {
              rotation -= 90;
          }
  
          // Prevent text from rotating upside down
          if (rotation > 90) {
              rotation -= 180;
          } else if (rotation < -90) {
              rotation += 180;
          }
  
          options.rotation = rotation;
      }
      // NOTE: alignDataLabel positions the data label differntly when rotation is
      // 0. Avoiding this by setting rotation to a small number.
      if (options.rotation === 0) {
          options.rotation = 0.001;
      }
      return options;
  };
  
  var getAnimation = function getAnimation(shape, params) {
      var point = params.point,
          radians = params.radians,
          innerR = params.innerR,
          idRoot = params.idRoot,
          idPreviousRoot = params.idPreviousRoot,
          shapeExisting = params.shapeExisting,
          shapeRoot = params.shapeRoot,
          shapePreviousRoot = params.shapePreviousRoot,
          visible = params.visible,
          from = {},
          to = {
              end: shape.end,
              start: shape.start,
              innerR: shape.innerR,
              r: shape.r,
              x: shape.x,
              y: shape.y
          };
  
      if (visible) {
          // Animate points in
          if (!point.graphic && shapePreviousRoot) {
              if (idRoot === point.id) {
                  from = {
                      start: radians.start,
                      end: radians.end
                  };
              } else {
                  from = (shapePreviousRoot.end <= shape.start) ? {
                      start: radians.end,
                      end: radians.end
                  } : {
                      start: radians.start,
                      end: radians.start
                  };
              }
              // Animate from center and outwards.
              from.innerR = from.r = innerR;
          }
      } else {
          // Animate points out
          if (point.graphic) {
              if (idPreviousRoot === point.id) {
                  to = {
                      innerR: innerR,
                      r: innerR
                  };
              } else if (shapeRoot) {
                  to = (shapeRoot.end <= shapeExisting.start) ?
                      {
                          innerR: innerR,
                          r: innerR,
                          start: radians.end,
                          end: radians.end
                      } : {
                          innerR: innerR,
                          r: innerR,
                          start: radians.start,
                          end: radians.start
                      };
              }
          }
      }
      return {
          from: from,
          to: to
      };
  };
  
  var getDrillId = function getDrillId(point, idRoot, mapIdToNode) {
      var drillId,
          node = point.node,
          nodeRoot;
  
      if (!node.isLeaf) {
          // When it is the root node, the drillId should be set to parent.
          if (idRoot === point.id) {
              nodeRoot = mapIdToNode[idRoot];
              drillId = nodeRoot.parent;
          } else {
              drillId = point.id;
          }
      }
      return drillId;
  };
  
  var cbSetTreeValuesBefore = function before(node, options) {
      var mapIdToNode = options.mapIdToNode,
          nodeParent = mapIdToNode[node.parent],
          series = options.series,
          chart = series.chart,
          points = series.points,
          point = points[node.i],
          colors = (series.options.colors || chart && chart.options.colors),
          colorInfo = getColor(node, {
              colors: colors,
              colorIndex: series.colorIndex,
              index: options.index,
              mapOptionsToLevel: options.mapOptionsToLevel,
              parentColor: nodeParent && nodeParent.color,
              parentColorIndex: nodeParent && nodeParent.colorIndex,
              series: options.series,
              siblings: options.siblings
          });
  
      node.color = colorInfo.color;
      node.colorIndex = colorInfo.colorIndex;
      if (point) {
          point.color = node.color;
          point.colorIndex = node.colorIndex;
          // Set slicing on node, but avoid slicing the top node.
          node.sliced = (node.id !== options.idRoot) ? point.sliced : false;
      }
      return node;
  };
  
  /**
   * A Sunburst displays hierarchical data, where a level in the hierarchy is
   * represented by a circle. The center represents the root node of the tree.
   * The visualization bears a resemblance to both treemap and pie charts.
   *
   * @sample highcharts/demo/sunburst
   *         Sunburst chart
   *
   * @extends      plotOptions.pie
   * @excluding    allAreas, clip, colorAxis, compare, compareBase, dataGrouping,
   *               depth, endAngle, gapSize, gapUnit, ignoreHiddenPoint,
   *               innerSize, joinBy, legendType, linecap, minSize,
   *               navigatorOptions, pointRange
   * @product      highcharts
   * @optionparent plotOptions.sunburst
   * @private
   */
  var sunburstOptions = {
  
      /**
       * Set options on specific levels. Takes precedence over series options,
       * but not point options.
       *
       * @sample highcharts/demo/sunburst
       *         Sunburst chart
       *
       * @type      {Array<*>}
       * @apioption plotOptions.sunburst.levels
       */
  
      /**
       * Can set a `borderColor` on all points which lies on the same level.
       *
       * @type      {Highcharts.ColorString}
       * @apioption plotOptions.sunburst.levels.borderColor
       */
  
      /**
       * Can set a `borderWidth` on all points which lies on the same level.
       *
       * @type      {number}
       * @apioption plotOptions.sunburst.levels.borderWidth
       */
  
      /**
       * Can set a `borderDashStyle` on all points which lies on the same level.
       *
       * @type      {string}
       * @apioption plotOptions.sunburst.levels.borderDashStyle
       */
  
      /**
       * Can set a `color` on all points which lies on the same level.
       *
       * @type      {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
       * @apioption plotOptions.sunburst.levels.color
       */
  
      /**
       * Can set a `colorVariation` on all points which lies on the same level.
       *
       * @apioption plotOptions.sunburst.levels.colorVariation
       */
  
      /**
       * The key of a color variation. Currently supports `brightness` only.
       *
       * @type      {string}
       * @apioption plotOptions.sunburst.levels.colorVariation.key
       */
  
      /**
       * The ending value of a color variation. The last sibling will receive this
       * value.
       *
       * @type      {number}
       * @apioption plotOptions.sunburst.levels.colorVariation.to
       */
  
      /**
       * Can set `dataLabels` on all points which lies on the same level.
       *
       * @type      {object}
       * @apioption plotOptions.sunburst.levels.dataLabels
       */
  
      /**
       * Can set a `levelSize` on all points which lies on the same level.
       *
       * @type      {object}
       * @apioption plotOptions.sunburst.levels.levelSize
       */
  
      /**
       * Can set a `rotation` on all points which lies on the same level.
       *
       * @type      {number}
       * @apioption plotOptions.sunburst.levels.rotation
       */
  
      /**
       * Can set a `rotationMode` on all points which lies on the same level.
       *
       * @type      {string}
       * @apioption plotOptions.sunburst.levels.rotationMode
       */
  
      /**
       * When enabled the user can click on a point which is a parent and
       * zoom in on its children. Deprecated and replaced by
       * [allowTraversingTree](#plotOptions.sunburst.allowTraversingTree).
       *
       * @deprecated
       * @type      {boolean}
       * @default   false
       * @since     6.0.0
       * @product   highcharts
       * @apioption plotOptions.sunburst.allowDrillToNode
       */
  
      /**
       * When enabled the user can click on a point which is a parent and
       * zoom in on its children.
       *
       * @type      {boolean}
       * @default   false
       * @since     7.0.3
       * @product   highcharts
       * @apioption plotOptions.sunburst.allowTraversingTree
       */
  
      /**
       * The center of the sunburst chart relative to the plot area. Can be
       * percentages or pixel values.
       *
       * @sample {highcharts} highcharts/plotoptions/pie-center/
       *         Centered at 100, 100
       *
       * @type    {Array<number|string>}
       * @default ["50%", "50%"]
       * @product highcharts
       */
      center: ['50%', '50%'],
      colorByPoint: false,
      /**
       * Disable inherited opacity from Treemap series.
       *
       * @ignore-option
       */
      opacity: 1,
      /**
       * @type    {Highcharts.SeriesSunburstDataLabelsOptionsObject|Array<Highcharts.SeriesSunburstDataLabelsOptionsObject>}
       * @default {"allowOverlap": true, "defer": true, "rotationMode": "auto", "style": {"textOverflow": "ellipsis"}}
       */
      dataLabels: {
          /** @ignore-option */
          allowOverlap: true,
          /** @ignore-option */
          defer: true,
          /** @ignore-option */
          rotationMode: 'auto',
          /** @ignore-option */
          style: {
              textOverflow: 'ellipsis'
          }
      },
      /**
       * Which point to use as a root in the visualization.
       *
       * @type {string}
       */
      rootId: undefined,
  
      /**
       * Used together with the levels and `allowDrillToNode` options. When
       * set to false the first level visible when drilling is considered
       * to be level one. Otherwise the level will be the same as the tree
       * structure.
       */
      levelIsConstant: true,
  
      /**
       * Determines the width of the ring per level.
       *
       * @sample {highcharts} highcharts/plotoptions/sunburst-levelsize/
       *         Sunburst with various sizes per level
       *
       * @since 6.0.5
       */
      levelSize: {
          /**
           * The value used for calculating the width of the ring. Its' affect is
           * determined by `levelSize.unit`.
           *
           * @sample {highcharts} highcharts/plotoptions/sunburst-levelsize/
           *         Sunburst with various sizes per level
           */
          value: 1,
          /**
           * How to interpret `levelSize.value`.
           *
           * - `percentage` gives a width relative to result of outer radius minus
           *   inner radius.
           *
           * - `pixels` gives the ring a fixed width in pixels.
           *
           * - `weight` takes the remaining width after percentage and pixels, and
           *   distributes it accross all "weighted" levels. The value relative to
           *   the sum of all weights determines the width.
           *
           * @sample {highcharts} highcharts/plotoptions/sunburst-levelsize/
           *         Sunburst with various sizes per level
           *
           * @validvalue ["percentage", "pixels", "weight"]
           */
          unit: 'weight'
      },
  
      /**
       * Options for the button appearing when traversing down in a treemap.
       *
       * @extends plotOptions.treemap.traverseUpButton
       * @since 6.0.0
       * @apioption plotOptions.sunburst.traverseUpButton
       */
  
      /**
       * If a point is sliced, moved out from the center, how many pixels
       * should it be moved?.
       *
       * @sample highcharts/plotoptions/sunburst-sliced
       *         Sliced sunburst
       *
       * @since 6.0.4
       */
      slicedOffset: 10
  };
  
  // Properties of the Sunburst series.
  var sunburstSeries = {
      drawDataLabels: noop, // drawDataLabels is called in drawPoints
      drawPoints: function drawPoints() {
          var series = this,
              mapOptionsToLevel = series.mapOptionsToLevel,
              shapeRoot = series.shapeRoot,
              group = series.group,
              hasRendered = series.hasRendered,
              idRoot = series.rootNode,
              idPreviousRoot = series.idPreviousRoot,
              nodeMap = series.nodeMap,
              nodePreviousRoot = nodeMap[idPreviousRoot],
              shapePreviousRoot = nodePreviousRoot && nodePreviousRoot.shapeArgs,
              points = series.points,
              radians = series.startAndEndRadians,
              chart = series.chart,
              optionsChart = chart && chart.options && chart.options.chart || {},
              animation = (
                  isBoolean(optionsChart.animation) ?
                      optionsChart.animation :
                      true
              ),
              positions = series.center,
              center = {
                  x: positions[0],
                  y: positions[1]
              },
              innerR = positions[3] / 2,
              renderer = series.chart.renderer,
              animateLabels,
              animateLabelsCalled = false,
              addedHack = false,
              hackDataLabelAnimation = !!(
                  animation &&
                  hasRendered &&
                  idRoot !== idPreviousRoot &&
                  series.dataLabelsGroup
              );
  
          if (hackDataLabelAnimation) {
              series.dataLabelsGroup.attr({ opacity: 0 });
              animateLabels = function () {
                  var s = series;
  
                  animateLabelsCalled = true;
                  if (s.dataLabelsGroup) {
                      s.dataLabelsGroup.animate({
                          opacity: 1,
                          visibility: 'visible'
                      });
                  }
              };
          }
          points.forEach(function (point) {
              var node = point.node,
                  level = mapOptionsToLevel[node.level],
                  shapeExisting = point.shapeExisting || {},
                  shape = node.shapeArgs || {},
                  animationInfo,
                  onComplete,
                  visible = !!(node.visible && node.shapeArgs);
  
              if (hasRendered && animation) {
                  animationInfo = getAnimation(shape, {
                      center: center,
                      point: point,
                      radians: radians,
                      innerR: innerR,
                      idRoot: idRoot,
                      idPreviousRoot: idPreviousRoot,
                      shapeExisting: shapeExisting,
                      shapeRoot: shapeRoot,
                      shapePreviousRoot: shapePreviousRoot,
                      visible: visible
                  });
              } else {
                  // When animation is disabled, attr is called from animation.
                  animationInfo = {
                      to: shape,
                      from: {}
                  };
              }
              extend(point, {
                  shapeExisting: shape, // Store for use in animation
                  tooltipPos: [shape.plotX, shape.plotY],
                  drillId: getDrillId(point, idRoot, nodeMap),
                  name: '' + (point.name || point.id || point.index),
                  plotX: shape.plotX, // used for data label position
                  plotY: shape.plotY, // used for data label position
                  value: node.val,
                  isNull: !visible // used for dataLabels & point.draw
              });
              point.dlOptions = getDlOptions({
                  point: point,
                  level: level,
                  optionsPoint: point.options,
                  shapeArgs: shape
              });
              if (!addedHack && visible) {
                  addedHack = true;
                  onComplete = animateLabels;
              }
              point.draw({
                  animatableAttribs: animationInfo.to,
                  attribs: extend(
                      animationInfo.from,
                      !chart.styledMode && series.pointAttribs(
                          point,
                          point.selected && 'select'
                      )
                  ),
                  onComplete: onComplete,
                  group: group,
                  renderer: renderer,
                  shapeType: 'arc',
                  shapeArgs: shape
              });
          });
          // Draw data labels after points
          // TODO draw labels one by one to avoid addtional looping
          if (hackDataLabelAnimation && addedHack) {
              series.hasRendered = false;
              series.options.dataLabels.defer = true;
              Series.prototype.drawDataLabels.call(series);
              series.hasRendered = true;
              // If animateLabels is called before labels were hidden, then call
              // it again.
              if (animateLabelsCalled) {
                  animateLabels();
              }
          } else {
              Series.prototype.drawDataLabels.call(series);
          }
      },
  
      pointAttribs: seriesTypes.column.prototype.pointAttribs,
  
      // The layout algorithm for the levels
      layoutAlgorithm: layoutAlgorithm,
  
      // Set the shape arguments on the nodes. Recursive from root down.
      setShapeArgs: function (parent, parentValues, mapOptionsToLevel) {
          var childrenValues = [],
              level = parent.level + 1,
              options = mapOptionsToLevel[level],
              // Collect all children which should be included
              children = parent.children.filter(function (n) {
                  return n.visible;
              }),
              twoPi = 6.28; // Two times Pi.
  
          childrenValues = this.layoutAlgorithm(parentValues, children, options);
          children.forEach(function (child, index) {
              var values = childrenValues[index],
                  angle = values.start + ((values.end - values.start) / 2),
                  radius = values.innerR + ((values.r - values.innerR) / 2),
                  radians = (values.end - values.start),
                  isCircle = (values.innerR === 0 && radians > twoPi),
                  center = (
                      isCircle ?
                          { x: values.x, y: values.y } :
                          getEndPoint(values.x, values.y, angle, radius)
                  ),
                  val = (
                      child.val ?
                          (
                              child.childrenTotal > child.val ?
                                  child.childrenTotal :
                                  child.val
                          ) :
                          child.childrenTotal
                  );
  
              // The inner arc length is a convenience for data label filters.
              if (this.points[child.i]) {
                  this.points[child.i].innerArcLength = radians * values.innerR;
                  this.points[child.i].outerArcLength = radians * values.r;
              }
  
              child.shapeArgs = merge(values, {
                  plotX: center.x,
                  plotY: center.y + 4 * Math.abs(Math.cos(angle))
              });
              child.values = merge(values, {
                  val: val
              });
              // If node has children, then call method recursively
              if (child.children.length) {
                  this.setShapeArgs(child, child.values, mapOptionsToLevel);
              }
          }, this);
      },
  
  
      translate: function translate() {
          var series = this,
              options = series.options,
              positions = series.center = getCenter.call(series),
              radians = series.startAndEndRadians = getStartAndEndRadians(
                  options.startAngle,
                  options.endAngle
              ),
              innerRadius = positions[3] / 2,
              outerRadius = positions[2] / 2,
              diffRadius = outerRadius - innerRadius,
              // NOTE: updateRootId modifies series.
              rootId = updateRootId(series),
              mapIdToNode = series.nodeMap,
              mapOptionsToLevel,
              idTop,
              nodeRoot = mapIdToNode && mapIdToNode[rootId],
              nodeTop,
              tree,
              values,
              nodeIds = {};
  
          series.shapeRoot = nodeRoot && nodeRoot.shapeArgs;
          // Call prototype function
          Series.prototype.translate.call(series);
          // @todo Only if series.isDirtyData is true
          tree = series.tree = series.getTree();
  
          // Render traverseUpButton, after series.nodeMap i calculated.
          series.renderTraverseUpButton(rootId);
          mapIdToNode = series.nodeMap;
          nodeRoot = mapIdToNode[rootId];
          idTop = isString(nodeRoot.parent) ? nodeRoot.parent : '';
          nodeTop = mapIdToNode[idTop];
          mapOptionsToLevel = getLevelOptions({
              from: nodeRoot.level > 0 ? nodeRoot.level : 1,
              levels: series.options.levels,
              to: tree.height,
              defaults: {
                  colorByPoint: options.colorByPoint,
                  dataLabels: options.dataLabels,
                  levelIsConstant: options.levelIsConstant,
                  levelSize: options.levelSize,
                  slicedOffset: options.slicedOffset
              }
          });
          // NOTE consider doing calculateLevelSizes in a callback to
          // getLevelOptions
          mapOptionsToLevel = calculateLevelSizes(mapOptionsToLevel, {
              diffRadius: diffRadius,
              from: nodeRoot.level > 0 ? nodeRoot.level : 1,
              to: tree.height
          });
          // TODO Try to combine setTreeValues & setColorRecursive to avoid
          //  unnecessary looping.
          setTreeValues(tree, {
              before: cbSetTreeValuesBefore,
              idRoot: rootId,
              levelIsConstant: options.levelIsConstant,
              mapOptionsToLevel: mapOptionsToLevel,
              mapIdToNode: mapIdToNode,
              points: series.points,
              series: series
          });
          values = mapIdToNode[''].shapeArgs = {
              end: radians.end,
              r: innerRadius,
              start: radians.start,
              val: nodeRoot.val,
              x: positions[0],
              y: positions[1]
          };
          this.setShapeArgs(nodeTop, values, mapOptionsToLevel);
          // Set mapOptionsToLevel on series for use in drawPoints.
          series.mapOptionsToLevel = mapOptionsToLevel;
  
          // #10669 - verify if all nodes have unique ids
          series.data.forEach(function (child) {
              if (nodeIds[child.id]) {
                  H.error(31, false, series.chart);
              }
              // map
              nodeIds[child.id] = true;
          });
  
          // reset object
          nodeIds = {};
      },
  
      // Animate the slices in. Similar to the animation of polar charts.
      animate: function (init) {
          var chart = this.chart,
              center = [
                  chart.plotWidth / 2,
                  chart.plotHeight / 2
              ],
              plotLeft = chart.plotLeft,
              plotTop = chart.plotTop,
              attribs,
              group = this.group;
  
          // Initialize the animation
          if (init) {
  
              // Scale down the group and place it in the center
              attribs = {
                  translateX: center[0] + plotLeft,
                  translateY: center[1] + plotTop,
                  scaleX: 0.001, // #1499
                  scaleY: 0.001,
                  rotation: 10,
                  opacity: 0.01
              };
  
              group.attr(attribs);
  
          // Run the animation
          } else {
              attribs = {
                  translateX: plotLeft,
                  translateY: plotTop,
                  scaleX: 1,
                  scaleY: 1,
                  rotation: 0,
                  opacity: 1
              };
              group.animate(attribs, this.options.animation);
  
              // Delete this function to allow it only once
              this.animate = null;
          }
      },
      utils: {
          calculateLevelSizes: calculateLevelSizes,
          range: range
      }
  };
  
  // Properties of the Sunburst series.
  var sunburstPoint = {
      draw: drawPoint,
      shouldDraw: function shouldDraw() {
          return !this.isNull;
      },
      isValid: function isValid() {
          return true;
      }
  };
  
  /**
   * A `sunburst` series. If the [type](#series.sunburst.type) option is
   * not specified, it is inherited from [chart.type](#chart.type).
   *
   * @extends   series,plotOptions.sunburst
   * @excluding dataParser, dataURL, stack
   * @product   highcharts
   * @apioption series.sunburst
   */
  
  /**
   * @type      {Array<number|null|*>}
   * @extends   series.treemap.data
   * @excluding x, y
   * @product   highcharts
   * @apioption series.sunburst.data
   */
  
  /**
   * @type      {Highcharts.SeriesSunburstDataLabelsOptionsObject|Array<Highcharts.SeriesSunburstDataLabelsOptionsObject>}
   * @product   highcharts
   * @apioption series.sunburst.data.dataLabels
   */
  
  /**
   * The value of the point, resulting in a relative area of the point
   * in the sunburst.
   *
   * @type      {number|null}
   * @since     6.0.0
   * @product   highcharts
   * @apioption series.sunburst.data.value
   */
  
  /**
   * Use this option to build a tree structure. The value should be the id of the
   * point which is the parent. If no points has a matching id, or this option is
   * undefined, then the parent will be set to the root.
   *
   * @type      {string}
   * @since     6.0.0
   * @product   highcharts
   * @apioption series.treemap.data.parent
   */
  
  /**
    * Whether to display a slice offset from the center. When a sunburst point is
    * sliced, its children are also offset.
    *
    * @sample highcharts/plotoptions/sunburst-sliced
    *         Sliced sunburst
    *
    * @type      {boolean}
    * @default   false
    * @since     6.0.4
    * @product   highcharts
    * @apioption series.sunburst.data.sliced
    */
  
  /**
   * @private
   * @class
   * @name Highcharts.seriesTypes.sunburst
   *
   * @augments Highcharts.Series
   */
  seriesType(
      'sunburst',
      'treemap',
      sunburstOptions,
      sunburstSeries,
      sunburstPoint
  );
  