Last active
August 18, 2021 19:18
-
-
Save juskek/07c0139ff6e13ff288b001976c25c44b to your computer and use it in GitHub Desktop.
Code used to implement animations into CustomPainter.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class RadarChartTransition extends AnimatedWidget { | |
final AnimationController controller; | |
final int nodes; | |
final int segments; | |
final List<double> data; | |
final List<String> labels; | |
// CONSTRUCTOR | |
RadarChartTransition( | |
this.controller, | |
this.nodes, | |
this.segments, | |
this.data, | |
this.labels, | |
) : super(listenable: controller); | |
// GETTER | |
AnimationController get _animationController => | |
listenable as AnimationController; | |
// TWEEN | |
// tween sequence for popping effect | |
final TweenSequence<double> poppingTweenSeq = TweenSequence<double>( | |
<TweenSequenceItem<double>>[ | |
TweenSequenceItem<double>( | |
tween: Tween<double>(begin: 0, end: 1.5) | |
.chain(CurveTween(curve: Curves.easeIn)), | |
weight: 30, | |
), | |
TweenSequenceItem<double>( | |
tween: Tween<double>(begin: 1.5, end: 1) | |
.chain(CurveTween(curve: Curves.bounceOut)), | |
weight: 70, | |
), | |
], | |
); | |
// tween for fast to slow effect | |
final Tween<double> easeOutTween = Tween(begin: 0, end: 1); | |
// function which returns the tween sequence as a part of the animation progress | |
Animation<double> _tweenSeqVal(start, end) { | |
return poppingTweenSeq.animate( | |
CurvedAnimation( | |
parent: _animationController, | |
curve: Interval( | |
start, | |
end, | |
curve: Curves.ease, | |
), | |
), | |
); | |
} | |
// function which returns the tween as part of the animation progress | |
Animation<double> _tweenVal(start, end) { | |
return easeOutTween.animate( | |
CurvedAnimation( | |
parent: _animationController, | |
curve: Interval( | |
start, | |
end, | |
curve: Curves.easeOut, | |
), | |
), | |
); | |
} | |
// BUILD | |
@override | |
Widget build(BuildContext context) { | |
if (data.length != nodes) { | |
throw ('Data length and number of nodes are not equal!'); | |
} | |
// half the time for branches, half the time for nodes and segments | |
int totalSegments = segments * nodes; | |
double branchPhaseProgress = 0.5 / nodes; | |
double nodePhaseProgress = 0.5 / nodes; | |
double segPhaseProgress = 0.5 / totalSegments; | |
double polyPhaseProgress = 0.2 / totalSegments; | |
double branchStartProg = 0; | |
double nodeStartProg = 0.2; | |
double segStartProg = 0.4; | |
double polyStartProg = 0.8; | |
// List of tween values for branches | |
List<double> branchTweenVal = []; | |
for (var i = 0; i < nodes; i++) { | |
double startProgress = branchStartProg + branchPhaseProgress * i; | |
double endProgress = branchStartProg + branchPhaseProgress * (i + 1); | |
double tweenValueTemp = _tweenSeqVal(startProgress, endProgress).value; | |
branchTweenVal.add(tweenValueTemp); | |
} | |
// List of tween values for nodes | |
List<double> nodeTweenVal = []; | |
for (var i = 0; i < nodes; i++) { | |
double startProgress = nodeStartProg + nodePhaseProgress * i; | |
double endProgress = nodeStartProg + nodePhaseProgress * (i + 1); | |
double tweenValueTemp = _tweenSeqVal(startProgress, endProgress).value; | |
nodeTweenVal.add(tweenValueTemp); | |
} | |
// List of tween values for segments | |
// needs to be repeated for each layer of segment, for each branch | |
List<double> segTweenVal = []; | |
for (var j = 0; j < totalSegments; j++) { | |
double startProgress = segStartProg + segPhaseProgress * j; | |
double endProgress = segStartProg + segPhaseProgress * (j + 1); | |
double tweenValueTemp = _tweenSeqVal(startProgress, endProgress).value; | |
segTweenVal.add(tweenValueTemp); | |
} | |
double polyTweenVal = | |
_tweenVal(polyStartProg, (polyStartProg + polyPhaseProgress)).value; | |
return CustomPaint( | |
painter: RadarChartPainter(branchTweenVal, nodeTweenVal, segTweenVal, | |
polyTweenVal, nodes, segments, data, labels), | |
); | |
} | |
} | |
class RadarChartPainter extends CustomPainter { | |
final List<double> branchAniProgress; | |
final List<double> nodeAniProgress; | |
final List<double> segAniProgress; | |
final double polyAniProgress; | |
final int nodes; | |
final int segments; | |
final List<double> data; | |
final List<String> labels; | |
RadarChartPainter( | |
this.branchAniProgress, | |
this.nodeAniProgress, | |
this.segAniProgress, | |
this.polyAniProgress, | |
this.nodes, | |
this.segments, | |
this.data, | |
this.labels, | |
); | |
@override | |
void paint(ui.Canvas canvas, ui.Size size) { | |
final Offset centerPoint = Offset(0, 0); | |
// math for each branch | |
final angle = 360 / nodes; | |
// list of branch end points (x,y) | |
List<List<double>> points = []; | |
// for each branch | |
for (int i = 0; i < nodes; i++) { | |
final double lineLength = 200 * branchAniProgress[i]; | |
// paint styles | |
// branch | |
final lineStyle = Paint() | |
..strokeWidth = 3 * branchAniProgress[i] | |
..color = Colors.black; | |
// node | |
final nodeRadius = 5 * nodeAniProgress[i]; | |
final nodeStyle = Paint()..color = Colors.black; | |
if (i == 0) { | |
// start at 12 o clock | |
points.add([0, -lineLength]); | |
} else { | |
// everything else | |
final currentAngle = i * angle * pi / 180; | |
final x = lineLength * sin(currentAngle); // x | |
final y = -lineLength * cos(currentAngle); // y | |
points.add([x, y]); | |
} | |
// draw branch | |
Offset endPoint = Offset(points[i][0], points[i][1]); | |
canvas.drawLine(centerPoint, endPoint, lineStyle); | |
// draw node | |
canvas.drawCircle(endPoint, nodeRadius, nodeStyle); | |
} | |
// draw segments | |
int totalSegments = segments * nodes; | |
int i = 0; | |
for (var j = 0; j < totalSegments; j++) { | |
if (j >= 0 && j < nodes) { | |
i++; | |
if (i >= nodes) { | |
i = 0; | |
} | |
continue; // skip nodes at center | |
} | |
// segment | |
final segRadius = 2.5 * segAniProgress[j]; | |
// segment coord = offset branch (i) * segment no. (0, 1, 2, 3) | |
final segStyle = Paint()..color = Colors.black; | |
var segNo = (j / nodes).floor() / segments; | |
Offset segPoint = Offset(points[i][0], points[i][1]) * segNo.toDouble(); | |
canvas.drawCircle(segPoint, segRadius, segStyle); | |
i++; | |
if (i >= nodes) { | |
i = 0; | |
} | |
} | |
// draw poly | |
final polyStyle = Paint() | |
..strokeWidth = 2 | |
..color = Colors.grey.withAlpha(100); | |
List<Offset> dataOffsets = []; | |
for (var i = 0; i < nodes; i++) { | |
var temp = Offset(points[i][0], points[i][1]) * data[i] * polyAniProgress; | |
dataOffsets.add(temp); | |
} | |
Path polyPath = Path(); | |
polyPath.addPolygon(dataOffsets, true); | |
canvas.drawPath(polyPath, polyStyle); | |
// draw labels | |
double fontHeight = 12.0; | |
TextStyle style = TextStyle( | |
color: Colors.grey.withAlpha((255 * polyAniProgress).toInt()), | |
fontSize: fontHeight, | |
fontStyle: FontStyle.normal, | |
fontWeight: FontWeight.normal, | |
fontFamily: 'Open Sans', | |
); | |
for (var i = 0; i < nodes; i++) { | |
final paraBuilder = ui.ParagraphBuilder(ui.ParagraphStyle( | |
fontSize: style.fontSize, | |
fontFamily: style.fontFamily, | |
fontStyle: style.fontStyle, | |
fontWeight: style.fontWeight, | |
textAlign: TextAlign.center, | |
)) | |
..pushStyle(style.getTextStyle()); | |
paraBuilder.addText(labels[i]); | |
final ui.Paragraph labelPara = paraBuilder.build() | |
..layout(ui.ParagraphConstraints(width: size.width)); | |
var temp = | |
Offset(points[i][0] - fontHeight / 4, points[i][1] - fontHeight / 2) * | |
1.1; | |
canvas.drawParagraph(labelPara, temp); | |
} | |
} | |
@override | |
bool shouldRepaint(CustomPainter oldDelegate) { | |
// hook called when CustomPainter is rebuilt | |
// when to repaint, set to true if necessary | |
return true; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment