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();

  // List of final points for the previous line in a stack.
  final previousPointList = <List<_DatumPoint<D>>>[];

  // List of initial points for the previous line in a stack, animated in from
  // the measure axis.
  final previousInitialPointList = <List<_DatumPoint<D>>>[];

  _mergeIntoSeriesMap(seriesList);

  for (final series in seriesList) {
    final domainAxis = series.getAttr(domainAxisKey)! as ImmutableAxis<D>;
    final lineKey = series.id;
    final stackIndex = series.getAttr(lineStackIndexKey)!;

    previousPointList.add([]);
    previousInitialPointList.add([]);

    final elementsList = _seriesLineMap[lineKey]!;

    final styleSegments = series.getAttr(styleSegmentsKey)!;

    // Include the end points of the domain axis range in the first and last
    // style segments to avoid clipping everything when the domain range of
    // the data is very small. Doing this after [preProcess] handles invalid
    // data (e.g. null measure) at the ends of the series data.
    //
    // TODO: Handle ordinal axes by looking at the next domains.
    if (styleSegments.isNotEmpty && domainAxis is! OrdinalAxis) {
      final drawBounds = this.drawBounds!;
      final startPx = (isRtl ? drawBounds.right : drawBounds.left).toDouble();
      final endPx = (isRtl ? drawBounds.left : drawBounds.right).toDouble();

      final startDomain = domainAxis.getDomain(startPx);
      final endDomain = domainAxis.getDomain(endPx);

      styleSegments.first.domainExtent.includePoint(startDomain);
      styleSegments.last.domainExtent.includePoint(endDomain);
    }

    // Create a set of animated line and area elements for each style segment.
    //
    // If the series contains null measure values, then multiple animated line
    // and area objects will be created to represent the isolated sections of
    // the series.
    //
    // The full set of line and area elements will be rendered on the canvas
    // for each style segment, with a clip region added in the [paint] process
    // later to display only the relevant parts of data. This ensures that
    // styles that visually depend on the start location, such as dash
    // patterns, are not disrupted by other changes in style.
    for (final styleSegment in styleSegments) {
      final styleKey = styleSegment.styleKey;

      // If we already have an AnimatingPoint for that index, use it.
      var animatingElements = elementsList
          .firstWhereOrNull((elements) => elements.styleKey == styleKey);

      if (animatingElements != null) {
        previousInitialPointList[stackIndex] = animatingElements.allPoints;
      } else {
        // Create a new line and have it animate in from axis.
        final lineAndArea = _createLineAndAreaElements(
          series,
          styleSegment as _LineRendererElement<D>,
          stackIndex > 0 ? previousInitialPointList[stackIndex - 1] : null,
          true,
        );
        final lineElementList =
            lineAndArea[0] as List<_LineRendererElement<D>>;
        final areaElementList =
            lineAndArea[1] as List<_AreaRendererElement<D>>;
        final allPointList = lineAndArea[2] as List<_DatumPoint<D>>;
        final boundsElementList =
            lineAndArea[3] as List<_AreaRendererElement<D>>;

        // Create the line elements.
        final animatingLines = <_AnimatedLine<D>>[];

        for (var index = 0; index < lineElementList.length; index++) {
          animatingLines.add(
            _AnimatedLine<D>(
              key: lineElementList[index].styleKey,
              overlaySeries: series.overlaySeries,
            )..setNewTarget(lineElementList[index]),
          );
        }

        // Create the area elements.
        List<_AnimatedArea<D>>? animatingAreas;
        if (config.includeArea) {
          animatingAreas = <_AnimatedArea<D>>[];

          for (var index = 0; index < areaElementList.length; index++) {
            animatingAreas.add(
              _AnimatedArea<D>(
                key: areaElementList[index].styleKey,
                overlaySeries: series.overlaySeries,
              )..setNewTarget(areaElementList[index]),
            );
          }
        }

        // Create the bound elements separately from area elements, because
        // it needs to be rendered on top of the area elements.
        List<_AnimatedArea<D>>? animatingBounds;
        if (_hasMeasureBounds) {
          animatingBounds ??= <_AnimatedArea<D>>[];

          for (var index = 0; index < boundsElementList.length; index++) {
            animatingBounds.add(
              _AnimatedArea<D>(
                key: boundsElementList[index].styleKey,
                overlaySeries: series.overlaySeries,
              )..setNewTarget(boundsElementList[index]),
            );
          }
        }

        animatingElements = _AnimatedElements<D>(
          styleKey: styleSegment.styleKey,
          allPoints: allPointList,
          lines: animatingLines,
          areas: animatingAreas,
          bounds: animatingBounds,
        );

        elementsList.add(animatingElements);

        previousInitialPointList[stackIndex] = allPointList;
      }

      // Create a new line using the final point locations.
      final lineAndArea = _createLineAndAreaElements(
        series,
        styleSegment as _LineRendererElement<D>,
        stackIndex > 0 ? previousPointList[stackIndex - 1] : null,
        false,
      );
      final lineElementList = lineAndArea[0] as List<_LineRendererElement<D>>;
      final areaElementList = lineAndArea[1] as List<_AreaRendererElement<D>>;
      final allPointList = lineAndArea[2] as List<_DatumPoint<D>>;
      final boundsElementList =
          lineAndArea[3] as List<_AreaRendererElement<D>>;

      for (var index = 0; index < lineElementList.length; index++) {
        final lineElement = lineElementList[index];

        // Add a new animated line if we have more segments in this draw cycle
        // than we did in the previous chart draw cycle.
        // TODO: Nicer animations for incoming segments.
        if (index >= animatingElements.lines.length) {
          animatingElements.lines.add(
            _AnimatedLine<D>(
              key: lineElement.styleKey,
              overlaySeries: series.overlaySeries,
            ),
          );
        }
        animatingElements.lines[index].setNewTarget(lineElement);
      }

      if (config.includeArea) {
        for (var index = 0; index < areaElementList.length; index++) {
          final areaElement = areaElementList[index];

          // Add a new animated area if we have more segments in this draw
          // cycle than we did in the previous chart draw cycle.
          // TODO: Nicer animations for incoming segments.
          if (index >= animatingElements.areas!.length) {
            animatingElements.areas!.add(
              _AnimatedArea<D>(
                key: areaElement.styleKey,
                overlaySeries: series.overlaySeries,
              ),
            );
          }
          animatingElements.areas![index].setNewTarget(areaElement);
        }
      }

      if (_hasMeasureBounds) {
        for (var index = 0; index < boundsElementList.length; index++) {
          final boundElement = boundsElementList[index];

          // Add a new animated bound if we have more segments in this draw
          // cycle than we did in the previous chart draw cycle.
          // TODO: Nicer animations for incoming segments.
          if (index >= animatingElements.bounds!.length) {
            animatingElements.bounds!.add(
              _AnimatedArea<D>(
                key: boundElement.styleKey,
                overlaySeries: series.overlaySeries,
              ),
            );
          }
          animatingElements.bounds![index].setNewTarget(boundElement);
        }
      }

      animatingElements.allPoints = allPointList;

      // Save the line points for the current series so that we can use them
      // in the area skirt for the next stacked series.
      previousPointList[stackIndex] = allPointList;
    }
  }

  // Animate out lines that don't exist anymore.
  _seriesLineMap.forEach((key, elements) {
    for (final element in elements) {
      for (final line in element.lines) {
        if (!_currentKeys.contains(line.key)) {
          line.animateOut();
        }
      }
      if (element.areas != null) {
        for (final area in element.areas!) {
          if (!_currentKeys.contains(area.key)) {
            area.animateOut();
          }
        }
      }
      if (element.bounds != null) {
        for (final bound in element.bounds!) {
          if (!_currentKeys.contains(bound.key)) {
            bound.animateOut();
          }
        }
      }
    }
  });

  if (config.includePoints) {
    _pointRenderer.update(seriesList, isAnimatingThisDraw);
  }
}