drawAnimatedLine function
Draws an animated line on the canvas connecting two points on a sphere.
The line is drawn using the provided canvas
object and is animated based on the animationValue
.
The radius
parameter specifies the radius of the sphere.
The rotationY
and rotationZ
parameters control the rotation of the sphere.
The size
parameter represents the size of the canvas.
The hoverPoint
parameter is an optional offset representing the position of a hover point.
Returns a Map with a path and a Offset representing the drawn line, or null if the line is not visible.
Implementation
Map? drawAnimatedLine(
Canvas canvas,
AnimatedPointConnection connection,
double radius,
double rotationY,
double rotationZ,
double animationValue,
Size size,
Offset? hoverPoint) {
// Calculate 3D positions for the start and end points
Vector3 startCartesian3D =
getSpherePosition3D(connection.start, radius, rotationY, rotationZ);
Vector3 endCartesian3D =
getSpherePosition3D(connection.end, radius, rotationY, rotationZ);
// Project 3D positions to 2D canvas
final center = Offset(size.width / 2, size.height / 2);
Offset startCartesian2D =
Offset(center.dx + startCartesian3D.y, center.dy - startCartesian3D.z);
Offset endCartesian2D =
Offset(center.dx + endCartesian3D.y, center.dy - endCartesian3D.z);
// Check if points are on the visible side of the sphere
bool isStartVisible = startCartesian3D.x > 0;
bool isEndVisible = endCartesian3D.x > 0;
// Calculate control points for curvature
if (isStartVisible || isEndVisible) {
Paint paint = Paint()
..color = connection.style.color
..strokeWidth = connection.style.lineWidth
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke;
Path path = Path();
path.moveTo(startCartesian2D.dx, startCartesian2D.dy);
var midPoint = (startCartesian3D + endCartesian3D) / 2;
midPoint.normalize();
final angle = calculateCentralAngle(connection.start, connection.end);
midPoint.scale(((radius + (angle) * 10 * pi) * connection.curveScale));
final midPoint2D = Offset(center.dx + midPoint.y, center.dy - midPoint.z);
path.quadraticBezierTo(
midPoint2D.dx, midPoint2D.dy, endCartesian2D.dx, endCartesian2D.dy);
PathMetric pathMetric = path.computeMetrics().first;
double start = 0;
double end = pathMetric.length * animationValue;
if (!isStartVisible) {
Offset? intersection =
findCurveSphereIntersection(path, center, radius, 1, true);
if (intersection != null) {
// canvas.drawCircle(intersection, 5, paint);
final perc = 1 - getDrawPercentage(path, intersection) / 100;
// print(perc);
start = pathMetric.length - (perc * pathMetric.length);
} else {
return null;
}
} else if (!isEndVisible) {
Offset? intersection =
findCurveSphereIntersection(path, center, radius, 1, false);
if (intersection != null) {
final perc =
1 - getDrawPercentage(path, intersection, first: false) / 100;
end = (pathMetric.length - (perc * pathMetric.length)) * animationValue;
} else {
return null;
}
}
Path extractPath = pathMetric.extractPath(start, end);
final pathMetrics = extractPath.computeMetrics();
if (pathMetrics.isNotEmpty) {
switch (connection.style.type) {
case PointConnectionType.solid:
canvas.drawPath(extractPath, paint);
break;
case PointConnectionType.dashed:
PathMetric extractPathMetric = extractPath.computeMetrics().first;
double dashLength = connection.style.dashSize;
double gapLength = connection.style.spacing;
double animationOffset = connection.animationOffset;
double distance = animationOffset;
while (distance < extractPathMetric.length) {
final double startDash = distance;
final double endDash = startDash + dashLength;
if (endDash < extractPathMetric.length) {
final Tangent? startTangent =
extractPathMetric.getTangentForOffset(startDash);
final Tangent? endTangent =
extractPathMetric.getTangentForOffset(endDash);
if (startTangent != null && endTangent != null) {
final Offset startPoint = startTangent.position;
final Offset endPoint = endTangent.position;
canvas.drawLine(startPoint, endPoint, paint);
}
}
distance += dashLength + gapLength;
}
break;
case PointConnectionType.dotted:
PathMetric extractPathMetric = extractPath.computeMetrics().first;
double distance = connection.animationOffset;
while (distance < extractPathMetric.length) {
Tangent? tangent = extractPathMetric.getTangentForOffset(distance);
if (tangent != null) {
final Offset point = tangent.position;
canvas.drawCircle(point, connection.style.dotSize, paint);
}
distance += connection.style.spacing;
}
break;
}
}
double t = 0.5;
Offset realMidPoint = Offset(
pow(1 - t, 2) * startCartesian2D.dx +
2 * (1 - t) * t * midPoint2D.dx +
pow(t, 2) * endCartesian2D.dx,
pow(1 - t, 2) * startCartesian2D.dy +
2 * (1 - t) * t * midPoint2D.dy +
pow(t, 2) * endCartesian2D.dy,
);
// paint text on the midpoint
if ((connection.isLabelVisible &&
(connection.label?.isNotEmpty ?? false)) &&
connection.labelBuilder == null) {
paintText(connection.label ?? '', connection.labelTextStyle, realMidPoint,
size, canvas);
}
return {'path': extractPath, 'midPoint': realMidPoint};
}
return null;
}