preprocessSeries method
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);
  orderedSeriesList.forEach((MutableSeries<D> series) {
    var elements = <BaseBarRendererElement>[];
    var domainFn = series.domainFn;
    var measureFn = series.measureFn;
    var measureOffsetFn = series.measureOffsetFn;
    var fillPatternFn = series.fillPatternFn;
    var 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++) {
      dynamic datum = series.data[barIndex];
      final details = getBaseDetails(datum, barIndex);
      details.barStackIndex = 0;
      details.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;
        var domain = domainFn(barIndex);
        var 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.
        var domainToCategoryToDetailsMap = measure == null || measure >= 0
            ? posDomainToStackKeyToDetailsMap
            : negDomainToStackKeyToDetailsMap;
        var categoryToDetailsMap = domainToCategoryToDetailsMap.putIfAbsent(
            domain, () => <String, BaseBarRendererElement>{});
        var 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;
        var 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);
    series.setAttr(stackKeyKey, stackKey);
    series.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);
  seriesList.forEach((MutableSeries<D> series) {
    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);
      series.setAttr(previousBarGroupWeightKey, previousBarWeight);
      series.setAttr(allBarGroupWeightsKey, barWeights);
    }
  });
}