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();
final bounds = chart!.drawAreaBounds;
final center = Point<double>((bounds.left + bounds.width / 2).toDouble(),
(bounds.top + bounds.height / 2).toDouble());
final radius = bounds.height < bounds.width
? (bounds.height / 2).toDouble()
: (bounds.width / 2).toDouble();
if (config.arcRatio != null) {
if (config.arcRatio! < 0 || config.arcRatio! > 1) {
throw ArgumentError('arcRatio must be between 0 and 1');
}
}
seriesList.forEach((ImmutableSeries<D> series) {
var colorFn = series.colorFn;
var arcListKey = series.id;
var elementsList =
series.getAttr(arcElementsKey) as List<SunburstArcRendererElement<D>>;
var arcLists =
_seriesArcMap.putIfAbsent(arcListKey, () => <AnimatedArcList<D>>[]);
if (series.data.isEmpty) {
var arcList = AnimatedArcList<D>();
_seriesArcMap.putIfAbsent(arcListKey, () => [arcList]);
final innerRadius = _calculateRadii(radius).first;
// If the series is empty, set up the "no data" arc element. This should
// occupy the entire chart, and use the chart style's no data color.
final details = elementsList[0];
var arcKey = '__no_data__';
// If we already have an AnimatingArc for that index, use it.
var animatingArc =
arcList.arcs.firstWhereOrNull((arc) => arc.key == arcKey);
arcList.center = center;
arcList.radius = radius;
arcList.innerRadius = innerRadius;
arcList.series = series;
arcList.stroke = config.noDataColor;
arcList.strokeWidthPx = 0.0;
// If we don't have any existing arc element, create a new arc. Unlike
// real arcs, we should not animate the no data state in from 0.
if (animatingArc == null) {
animatingArc = AnimatedArc<D>(arcKey, null, null);
arcList.arcs.add(animatingArc);
} else {
animatingArc.datum = null;
animatingArc.domain = null;
}
// Update the set of arcs that still exist in the series data.
_currentKeys.add(arcKey);
// Get the arcElement we are going to setup.
// Optimization to prevent allocation in non-animating case.
final arcElement = SunburstArcRendererElement<D>(
startAngle: details.startAngle,
endAngle: details.endAngle,
color: config.noDataColor,
series: series);
animatingArc.setNewTarget(arcElement);
arcLists.add(arcList);
} else {
var previousEndAngle = config.startAngle;
// Create Arc and add to arcList for each of the node with depth
// within config.maxDisplayLevel
var root = series.data.first as TreeNode<Object>;
var maxDepth = 0;
root.visit((node) {
maxDepth = max(maxDepth, node.depth);
});
// Create arcLists up to min(maxDepth, config.maxDisplayLevel).
final maxDisplayLevel = min(maxDepth, config.maxDisplayLevel);
final displayLevel = min(maxDepth, config.initialDisplayLevel);
for (var i = 0; i < maxDisplayLevel; i++) {
var arcList =
arcLists.length > i ? arcLists[i] : AnimatedArcList<D>();
// Create arc for node that’s within the initial display level or
// selected nodes and its children up to the maxDisplayLevel.
for (var node in _nodeToArcRenderElementMap.keys.where((e) =>
e.depth == i + 1 &&
(e.depth <= displayLevel || _nodeToExpand.contains(e)))) {
final radii = _calculateRadii(radius, maxDisplayLevel, i + 1);
final innerRadius = radii.first;
final outerRadius = radii.last;
final arcIndex = series.data.indexOf(node);
final Object datum = series.data[arcIndex];
final details = _nodeToArcRenderElementMap[node];
final domainValue = details!.domain;
final isLeaf = !node.hasChildren ||
((node.depth == displayLevel || _nodeToExpand.contains(node)) &&
!_nodeToExpand.any((e) => node.children.contains(e)));
final isOuterMostRing = node.depth == maxDisplayLevel;
var arcKey = '${series.id}__${domainValue.toString()}';
// If we already have an AnimatingArc for that index, use it.
var animatingArc =
arcList.arcs.firstWhereOrNull((arc) => arc.key == arcKey);
arcList.center = center;
arcList.radius = outerRadius;
arcList.innerRadius = innerRadius;
arcList.series = series;
arcList.stroke = config.stroke;
arcList.strokeWidthPx = config.strokeWidthPx;
// If we don't have any existing arc element, create a new arc and
// have it animate in from the position of the previous arc's end
// angle. If there were no previous arcs, then animate everything in
// from 0.
if (animatingArc == null) {
animatingArc = AnimatedArc<D>(arcKey, datum, domainValue)
..setNewTarget(SunburstArcRendererElement<D>(
color: colorFn!(arcIndex),
startAngle: previousEndAngle,
endAngle: previousEndAngle,
index: arcIndex,
series: series,
isLeaf: isLeaf,
isOuterMostRing: isOuterMostRing));
arcList.arcs.add(animatingArc);
} else {
animatingArc.datum = datum;
previousEndAngle = animatingArc.previousArcEndAngle ?? 0.0;
}
animatingArc.domain = domainValue;
// Update the set of arcs that still exist in the series data.
_currentKeys.add(arcKey);
// Get the arcElement we are going to setup.
// Optimization to prevent allocation in non-animating case.
final arcElement = SunburstArcRendererElement<D>(
color: colorFn!(arcIndex),
startAngle: details.startAngle,
endAngle: details.endAngle,
index: arcIndex,
series: series,
isLeaf: isLeaf,
isOuterMostRing: isOuterMostRing);
animatingArc.setNewTarget(arcElement);
}
if (arcLists.length <= i && arcList.arcs.isNotEmpty) {
arcLists.add(arcList);
}
}
}
});
// Animate out arcs that don't exist anymore.
_seriesArcMap.forEach((String key, List<AnimatedArcList<D>> arcLists) {
for (var arcList in arcLists) {
for (var arcIndex = 0; arcIndex < arcList.arcs.length; arcIndex++) {
final arc = arcList.arcs[arcIndex];
final arcStartAngle = arc.previousArcStartAngle;
if (_currentKeys.contains(arc.key) != true) {
// Default to animating out to the top of the chart, clockwise, if
// there are no arcs that start past this arc.
var targetArcAngle = (2 * pi) + config.startAngle;
// Find the nearest start angle of the next arc that still exists in
// the data.
for (final nextArc in arcList.arcs
.where((arc) => _currentKeys.contains(arc.key))) {
final nextArcStartAngle = nextArc.newTargetArcStartAngle;
if (arcStartAngle! < nextArcStartAngle! &&
nextArcStartAngle < targetArcAngle) {
targetArcAngle = nextArcStartAngle;
}
}
arc.animateOut(targetArcAngle);
}
}
}
});
}