update method

  1. @override
void update(
  1. List<ImmutableSeries<D>> seriesList,
  2. bool isAnimatingThisDraw
)
override

Generates rendering data needed to paint the data on the chart.

This is called during the post layout phase of the chart draw cycle.

Implementation

@override
void update(List<ImmutableSeries<D>> seriesList, bool isAnimatingThisDraw) {
  _currentKeys.clear();

  final bounds = chart!.drawAreaBounds;

  final center = Point<double>(
    bounds.left + bounds.width / 2,
    bounds.top + bounds.height / 2,
  );

  final radius =
      bounds.height < bounds.width ? (bounds.height / 2) : (bounds.width / 2);

  if (config.arcRatio != null) {
    if (config.arcRatio! < 0 || config.arcRatio! > 1) {
      throw ArgumentError('arcRatio must be between 0 and 1');
    }
  }

  for (final series in seriesList) {
    final colorFn = series.colorFn;
    final arcListKey = series.id;
    final elementsList = series.getAttr(arcElementsKey)!
        as List<SunburstArcRendererElement<D>>;

    final arcLists =
        _seriesArcMap.putIfAbsent(arcListKey, () => <AnimatedArcList<D>>[]);
    if (series.data.isEmpty) {
      final arcList = AnimatedArcList<D>();
      _seriesArcMap.putIfAbsent(arcListKey, () => [arcList]);
      final innerRadius = _calculateRadii(radius).first;

      // If the series is empty, set up the "no data" arc element. This should
      // occupy the entire chart, and use the chart style's no data color.
      final details = elementsList[0];

      const arcKey = '__no_data__';

      // If we already have an AnimatingArc for that index, use it.
      var animatingArc =
          arcList.arcs.firstWhereOrNull((arc) => arc.key == arcKey);

      arcList
        ..center = center
        ..radius = radius
        ..innerRadius = innerRadius
        ..series = series
        ..stroke = config.noDataColor
        ..strokeWidthPx = 0.0;

      // If we don't have any existing arc element, create a new arc. Unlike
      // real arcs, we should not animate the no data state in from 0.
      if (animatingArc == null) {
        animatingArc = AnimatedArc<D>(arcKey, null, null);
        arcList.arcs.add(animatingArc);
      } else {
        animatingArc
          ..datum = null
          ..domain = null;
      }

      // Update the set of arcs that still exist in the series data.
      _currentKeys.add(arcKey);

      // Get the arcElement we are going to setup.
      // Optimization to prevent allocation in non-animating case.
      final arcElement = SunburstArcRendererElement<D>(
        startAngle: details.startAngle,
        endAngle: details.endAngle,
        color: config.noDataColor,
        series: series,
      );

      animatingArc.setNewTarget(arcElement);

      arcLists.add(arcList);
    } else {
      var previousEndAngle = config.startAngle;

      // Create Arc and add to arcList for each of the node with depth
      // within config.maxDisplayLevel
      final root = series.data.first as TreeNode<Object>;
      var maxDepth = 0;
      root.visit((node) {
        maxDepth = max(maxDepth, node.depth);
      });

      // Create arcLists up to min(maxDepth, config.maxDisplayLevel).
      final maxDisplayLevel = min(maxDepth, config.maxDisplayLevel);
      final displayLevel = min(maxDepth, config.initialDisplayLevel);
      for (var i = 0; i < maxDisplayLevel; i++) {
        final arcList =
            arcLists.length > i ? arcLists[i] : AnimatedArcList<D>();

        // Create arc for node that’s within the initial display level or
        // selected nodes and its children up to the maxDisplayLevel.
        for (final node in _nodeToArcRenderElementMap.keys.where(
          (e) =>
              e.depth == i + 1 &&
              (e.depth <= displayLevel || _nodeToExpand.contains(e)),
        )) {
          final radii = _calculateRadii(radius, maxDisplayLevel, i + 1);
          final innerRadius = radii.first;
          final outerRadius = radii.last;

          final arcIndex = series.data.indexOf(node);
          final datum = series.data[arcIndex];
          final details = _nodeToArcRenderElementMap[node];
          final domainValue = details!.domain;
          final isLeaf = !node.hasChildren ||
              ((node.depth == displayLevel || _nodeToExpand.contains(node)) &&
                  !_nodeToExpand.any((e) => node.children.contains(e)));
          final isOuterMostRing = node.depth == maxDisplayLevel;

          final arcKey = '${series.id}__$domainValue';

          // If we already have an AnimatingArc for that index, use it.
          var animatingArc =
              arcList.arcs.firstWhereOrNull((arc) => arc.key == arcKey);

          arcList
            ..center = center
            ..radius = outerRadius
            ..innerRadius = innerRadius
            ..series = series
            ..stroke = config.stroke
            ..strokeWidthPx = config.strokeWidthPx;

          // If we don't have any existing arc element, create a new arc and
          // have it animate in from the position of the previous arc's end
          // angle. If there were no previous arcs, then animate everything in
          // from 0.
          if (animatingArc == null) {
            animatingArc = AnimatedArc<D>(
              arcKey,
              datum,
              //TODO: dangerous casts
              domainValue as D?,
            )..setNewTarget(
                SunburstArcRendererElement<D>(
                  color: colorFn!(arcIndex),
                  startAngle: previousEndAngle,
                  endAngle: previousEndAngle,
                  index: arcIndex,
                  series: series,
                  isLeaf: isLeaf,
                  isOuterMostRing: isOuterMostRing,
                ),
              );

            arcList.arcs.add(animatingArc);
          } else {
            animatingArc.datum = datum;

            previousEndAngle = animatingArc.previousArcEndAngle ?? 0.0;
          }

          // TODO: this could be a dangerous cast
          animatingArc.domain = domainValue as D?;

          // Update the set of arcs that still exist in the series data.
          _currentKeys.add(arcKey);

          // Get the arcElement we are going to setup.
          // Optimization to prevent allocation in non-animating case.
          final arcElement = SunburstArcRendererElement<D>(
            color: colorFn!(arcIndex),
            startAngle: details.startAngle,
            endAngle: details.endAngle,
            index: arcIndex,
            series: series,
            isLeaf: isLeaf,
            isOuterMostRing: isOuterMostRing,
          );

          animatingArc.setNewTarget(arcElement);
        }
        if (arcLists.length <= i && arcList.arcs.isNotEmpty) {
          arcLists.add(arcList);
        }
      }
    }
  }

  // Animate out arcs that don't exist anymore.
  _seriesArcMap.forEach((key, arcLists) {
    for (final arcList in arcLists) {
      for (var arcIndex = 0; arcIndex < arcList.arcs.length; arcIndex++) {
        final arc = arcList.arcs[arcIndex];
        final arcStartAngle = arc.previousArcStartAngle;

        if (!_currentKeys.contains(arc.key)) {
          // Default to animating out to the top of the chart, clockwise, if
          // there are no arcs that start past this arc.
          var targetArcAngle = (2 * pi) + config.startAngle;

          // Find the nearest start angle of the next arc that still exists in
          // the data.
          for (final nextArc in arcList.arcs
              .where((arc) => _currentKeys.contains(arc.key))) {
            final nextArcStartAngle = nextArc.newTargetArcStartAngle;

            if (arcStartAngle! < nextArcStartAngle! &&
                nextArcStartAngle < targetArcAngle) {
              targetArcAngle = nextArcStartAngle;
            }
          }

          arc.animateOut(targetArcAngle);
        }
      }
    }
  });
}