preprocessSeries method

  1. @override
void preprocessSeries(
  1. List<MutableSeries<D>> seriesList
)
inherited

Pre-calculates some details for the series that will be needed later during the drawing phase.

Implementation

@override
void preprocessSeries(List<MutableSeries<D>> seriesList) {
  // If the orientation of the chart changed, delete all data from the last
  // draw cycle. This allows us to start in a fresh state, so that we do not
  // get bad animations from the previously drawn data.
  //
  // Ideally we should animate the old bars out smoothly in some ways, but
  // this was the cheapest option.
  if (_lastVertical != chart.vertical) {
    _barStackMap.clear();
    _currentKeys.clear();
    _currentGroupsStackKeys.clear();
  }

  _lastVertical = chart.vertical;

  var barGroupIndex = 0;

  // Maps used to store the final measure offset of the previous series, for
  // each domain value.
  final posDomainToStackKeyToDetailsMap =
      <D, Map<String, BaseBarRendererElement>>{};
  final negDomainToStackKeyToDetailsMap =
      <D, Map<String, BaseBarRendererElement>>{};
  final categoryToIndexMap = <String, int>{};

  // Keep track of the largest bar stack size. This should be 1 for grouped
  // bars, and it should be the size of the tallest stack for stacked or
  // grouped stacked bars.
  var maxBarStackSize = 0;

  final orderedSeriesList = getOrderedSeriesList(seriesList);

  for (final series in orderedSeriesList) {
    final elements = <BaseBarRendererElement>[];

    final domainFn = series.domainFn;
    final measureFn = series.measureFn;
    final measureOffsetFn = series.measureOffsetFn;
    final fillPatternFn = series.fillPatternFn;
    final strokeWidthPxFn = series.strokeWidthPxFn;

    series.dashPatternFn ??= (_) => config.dashPattern;

    // Identifies which stack the series will go in, by default a single
    // stack.
    var stackKey = '__defaultKey__';

    // Override the stackKey with seriesCategory if we are GROUPED_STACKED
    // so we have a way to choose which series go into which stacks.
    if (config.grouped && config.stacked) {
      if (series.seriesCategory != null) {
        stackKey = series.seriesCategory!;
      }

      if (categoryToIndexMap.containsKey(stackKey)) {
        barGroupIndex = categoryToIndexMap[stackKey]!;
      } else {
        barGroupIndex = categoryToIndexMap.length;
        categoryToIndexMap[stackKey] = barGroupIndex;
      }
    }

    var needsMeasureOffset = false;

    for (var barIndex = 0; barIndex < series.data.length; barIndex++) {
      final dynamic datum = series.data[barIndex];
      final details = getBaseDetails(datum, barIndex)
        ..barStackIndex = 0
        ..measureOffset = measureOffsetFn!(barIndex);

      if (fillPatternFn != null) {
        details.fillPattern = fillPatternFn(barIndex);
      } else {
        details.fillPattern = config.fillPattern;
      }

      if (strokeWidthPxFn != null) {
        details.strokeWidthPx = strokeWidthPxFn(barIndex)?.toDouble();
      } else {
        details.strokeWidthPx = config.strokeWidthPx;
      }

      // When stacking is enabled, adjust the measure offset for each domain
      // value in each series by adding up the measures and offsets of lower
      // series.
      if (config.stacked) {
        needsMeasureOffset = true;
        final domain = domainFn(barIndex);
        final measure = measureFn(barIndex);

        // We will render positive bars in one stack, and negative bars in a
        // separate stack. Keep track of the measure offsets for these stacks
        // independently.
        final domainToCategoryToDetailsMap = measure == null || measure >= 0
            ? posDomainToStackKeyToDetailsMap
            : negDomainToStackKeyToDetailsMap;

        final categoryToDetailsMap = domainToCategoryToDetailsMap.putIfAbsent(
          domain,
          () => <String, BaseBarRendererElement>{},
        );

        final prevDetail = categoryToDetailsMap[stackKey];

        if (prevDetail != null) {
          details.barStackIndex = prevDetail.barStackIndex! + 1;
        }

        details.cumulativeTotal = measure ?? 0;

        // Get the previous series' measure offset.
        var measureOffset = measureOffsetFn(barIndex)!;
        if (prevDetail != null) {
          measureOffset += prevDetail.measureOffsetPlusMeasure!;

          details.cumulativeTotal =
              details.cumulativeTotal! + prevDetail.cumulativeTotal!;
        }

        // And overwrite the details measure offset.
        details.measureOffset = measureOffset;
        final measureValue = measure ?? 0;
        details.measureOffsetPlusMeasure = measureOffset + measureValue;

        categoryToDetailsMap[stackKey] = details;
      }

      maxBarStackSize = max(maxBarStackSize, details.barStackIndex! + 1);

      elements.add(details);
    }

    if (needsMeasureOffset) {
      // Override the measure offset function to return the measure offset we
      // calculated for each datum. This already includes any measure offset
      // that was configured in the series data.
      series.measureOffsetFn = (index) => elements[index!].measureOffset!;
    }

    series
      ..setAttr(barGroupIndexKey, barGroupIndex)
      ..setAttr(stackKeyKey, stackKey)
      ..setAttr(barElementsKey, elements);

    if (config.grouped) {
      barGroupIndex++;
    }
  }

  // Compute number of bar groups. This must be done after we have processed
  // all of the series once, so that we know how many categories we have.
  var numBarGroups = 0;
  if (config.grouped && config.stacked) {
    // For grouped stacked bars, categoryToIndexMap effectively one list per
    // group of stacked bars.
    numBarGroups = categoryToIndexMap.length;
  } else if (config.stacked) {
    numBarGroups = 1;
  } else {
    numBarGroups = seriesList.length;
  }

  // Compute bar group weights.
  final barWeights = _calculateBarWeights(numBarGroups);

  for (final series in seriesList) {
    series.setAttr(barGroupCountKey, numBarGroups);

    if (barWeights.isNotEmpty) {
      final barGroupIndex = series.getAttr(barGroupIndexKey)!;
      final barWeight = barWeights[barGroupIndex];

      // In RTL mode, we need to grab the weights for the bars that follow
      // this datum in the series (instead of precede it). The first datum is
      // physically positioned on the canvas to the right of all the rest of
      // the bar group data that follows it.
      final previousBarWeights = isRtl
          ? barWeights.getRange(barGroupIndex + 1, numBarGroups)
          : barWeights.getRange(0, barGroupIndex);

      final previousBarWeight = previousBarWeights.isNotEmpty
          ? previousBarWeights.reduce((a, b) => a + b)
          : 0.0;

      series
        ..setAttr(barGroupWeightKey, barWeight)
        ..setAttr(previousBarGroupWeightKey, previousBarWeight)
        ..setAttr(allBarGroupWeightsKey, barWeights);
    }
  }
}