renderComplexPath method

  1. @override
void renderComplexPath(
  1. Canvas canvas,
  2. ChartTransform transform,
  3. ILineChartData lineData,
  4. bool isDynamicStroke,
  5. bool isDynamicPaint,
)
override

Implementation

@override
void renderComplexPath(Canvas canvas, ChartTransform transform, ILineChartData lineData, bool isDynamicStroke, bool isDynamicPaint) {
  if (!renderDynamicPaint(canvas, transform, lineData, isDynamicStroke, isDynamicPaint)) {

    final points = lineData.points;

    final globalSize = lineData.thickness.size;
    final globalHalfScreen = transform.scalar(globalSize) / 2;

    // ---- transform centerline to screen space ----

    final center = points.map((p) {
      final v = transform.v3(Vector3(p.x, p.fy, 0));
      return Offset(v.x, v.y);
    }).toList();

    final count = center.length;

    // ---- compute extra half thickness per point ----

    final halfExtra = List<double>.generate(count, (i) {

      final localSize = points[i].thickness?.size ?? globalSize;
      final localHalf = transform.scalar(localSize) / 2;

      return (localHalf - globalHalfScreen).clamp(0.0, double.infinity);
    });

    // ---- compute normals per segment ----

    final normals = <Offset>[];

    for (int i = 0; i < count - 1; i++) {

      final d = center[i + 1] - center[i];
      final len = d.distance;

      if (len <= 0.00001) {
        normals.add(const Offset(0, 0));
        continue;
      }

      final dir = d / len;

      normals.add(Offset(-dir.dy, dir.dx));
    }

    // ---- intersection helper ----

    Offset intersectLines(
        Offset p,
        Offset r,
        Offset q,
        Offset s,
        ) {

      final cross = r.dx * s.dy - r.dy * s.dx;

      if (cross.abs() < 0.00001) {
        return p;
      }

      final qp = q - p;

      final t = (qp.dx * s.dy - qp.dy * s.dx) / cross;

      return p + r * t;
    }

    // ---- build contours ----

    final top = <Offset>[];
    final bottom = <Offset>[];

    // first point
        {
      final n = normals.first;
      final h = halfExtra.first;

      top.add(center.first + n * h);
      bottom.add(center.first - n * h);
    }

    // interior points
    for (int i = 1; i < count - 1; i++) {

      final nPrev = normals[i - 1];
      final nNext = normals[i];

      final hPrev = halfExtra[i];
      final hNext = halfExtra[i];

      final topPrevPoint = center[i] + nPrev * hPrev;
      final topNextPoint = center[i] + nNext * hNext;

      final bottomPrevPoint = center[i] - nPrev * hPrev;
      final bottomNextPoint = center[i] - nNext * hNext;

      final dirPrev = center[i] - center[i - 1];
      final dirNext = center[i + 1] - center[i];

      final topJoin = intersectLines(
        topPrevPoint,
        dirPrev,
        topNextPoint,
        dirNext,
      );

      final bottomJoin = intersectLines(
        bottomPrevPoint,
        dirPrev,
        bottomNextPoint,
        dirNext,
      );

      top.add(topJoin);
      bottom.add(bottomJoin);
    }

    // last point
        {
      final n = normals.last;
      final h = halfExtra.last;

      top.add(center.last + n * h);
      bottom.add(center.last - n * h);
    }

    // ---- build final path ----

    final path = Path();

    path.moveTo(top.first.dx, top.first.dy);

    for (int i = 1; i < top.length; i++) {
      path.lineTo(top[i].dx, top[i].dy);
    }

    for (int i = bottom.length - 1; i >= 0; i--) {
      path.lineTo(bottom[i].dx, bottom[i].dy);
    }

    path.close();

    final bounds = path.getBounds();

    final fillPaint = buildFillPaint(transform, lineData);
    final strokePaint = buildStrokePaint(transform, lineData);

    if (isDynamicStroke) {
      Gradient global = globalMixedGradient(lineData.thickness, lineData.points);
      strokePaint.shader = fillPaint.shader = global.createShader(bounds);
    } else {
      paintThickness(fillPaint, bounds, lineData.thickness);
      paintThickness(strokePaint, bounds, lineData.thickness);
    }

    canvas.drawPath(path, fillPaint);
    canvas.drawPath(path, strokePaint);
  }
}