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