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 (var series in orderedSeriesList) {
    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);

  for (var 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);
      series.setAttr(previousBarGroupWeightKey, previousBarWeight);
      series.setAttr(allBarGroupWeightsKey, barWeights);
    }
  }
}