drawSmoothLine function

void drawSmoothLine(
  1. Canvas canvas, {
  2. required List<Offset> points,
  3. required Color color,
  4. required double baseline,
  5. required double progress,
  6. double strokeWidth = 6,
  7. bool fill = true,
  8. bool endDot = true,
  9. bool smooth = true,
})

Draws a single smooth line series with an optional gradient area fill, a tracing left-to-right reveal animation, and an optional highlighted end dot. The Flutter equivalent of DrawScope.drawSmoothLine.

Implementation

void drawSmoothLine(
  Canvas canvas, {
  required List<Offset> points,
  required Color color,
  required double baseline,
  required double progress,
  double strokeWidth = 6,
  bool fill = true,
  bool endDot = true,
  bool smooth = true,
}) {
  if (points.length < 2) return;
  final clamped = progress.clamp(0.0, 1.0);
  final linePath = smooth ? smoothPath(points) : polylinePath(points);

  final startX = points.first.dx;
  final endX = points.last.dx;
  final revealRight = startX + (endX - startX) * clamped;

  // Soft gradient area fill, clipped to the reveal frontier.
  if (fill) {
    var topY = double.infinity;
    for (final p in points) {
      if (p.dy < topY) topY = p.dy;
    }
    final fillPath = Path.from(linePath)
      ..lineTo(endX, baseline)
      ..lineTo(startX, baseline)
      ..close();
    canvas
      ..save()
      ..clipRect(
        Rect.fromLTWH(startX, topY, revealRight - startX, baseline - topY),
      )
      ..drawPath(
        fillPath,
        Paint()
          ..style = PaintingStyle.fill
          ..shader = ui.Gradient.linear(
            Offset(0, topY),
            Offset(0, baseline),
            [
              color.withValues(alpha: 0.32),
              color.withValues(alpha: 0.144),
              color.withValues(alpha: 0),
            ],
            const [0.0, 0.5, 1.0],
          ),
      )
      ..restore();
  }

  // Trace the stroke exactly up to the reveal frontier for a clean "drawing"
  // feel. Compute the path metrics once and reuse them for both the trim and
  // the end-dot tangent below (computeMetrics flattens the whole path, so doing
  // it once instead of twice halves the per-frame cost on line charts).
  final metrics = linePath.computeMetrics().toList();
  final drawn = Path();
  for (final metric in metrics) {
    drawn.addPath(metric.extractPath(0, metric.length * clamped), Offset.zero);
  }
  canvas.drawPath(
    drawn,
    Paint()
      ..color = color
      ..style = PaintingStyle.stroke
      ..strokeWidth = strokeWidth
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round,
  );

  // Glowing dot at the leading edge of the reveal.
  if (endDot && clamped > 0.001) {
    Offset? pos;
    for (final metric in metrics) {
      final t = metric.getTangentForOffset(metric.length * clamped);
      if (t != null) pos = t.position;
    }
    if (pos != null) {
      canvas
        ..drawCircle(
          pos,
          strokeWidth * 1.5,
          Paint()..color = const Color(0xFFFFFFFF),
        )
        ..drawCircle(pos, strokeWidth * 0.95, Paint()..color = color);
    }
  }
}