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