getArrow function

Arrow getArrow(
  1. double x0,
  2. double y0,
  3. double x1,
  4. double y1, {
  5. double bow = 0,
  6. double stretchMin = 0,
  7. double stretchMax = 420,
  8. double stretch = 0.5,
  9. double padStart = 0,
  10. double padEnd = 0,
  11. bool flip = false,
  12. bool straights = true,
  13. ArcDirection arcDirection = ArcDirection.Auto,
})

Implementation

Arrow getArrow(
  double x0,
  double y0,
  double x1,
  double y1, {
  double bow = 0,
  double stretchMin = 0,
  double stretchMax = 420,
  double stretch = 0.5,
  double padStart = 0,
  double padEnd = 0,
  bool flip = false,
  bool straights = true,
  ArcDirection arcDirection = ArcDirection.Auto,
}) {
  final angle = getAngle(x0, y0, x1, y1);
  final dist = getDistance(x0, y0, x1, y1);
  final angles = getAngliness(x0, y0, x1, y1);

  // Step 0 ⤜⤏ Should the arrow be straight?

  if (dist < (padStart + padEnd) * 2 || // Too short
          (bow == 0 && stretch == 0) || // No bow, no stretch
          (straights &&
              [0.0, 1.0, double.infinity].contains(angles)) // 45 degree angle
      ) {
    // ⤜⤏ Arrow is straight! Just pad start and end points.

    // Padding distances
    final ps = max(0.0, min(dist - padStart, padStart));
    final pe = max(0.0, min(dist - ps, padEnd));

    // Move start point toward end point
    var pp0 = projectPoint(x0, y0, angle, ps);
    final px0 = pp0.first;
    final py0 = pp0.last;

    // Move end point toward start point
    final pp1 = projectPoint(x1, y1, angle + pi, pe);
    final px1 = pp1.first;
    final py1 = pp1.last;

    // Get midpoint between new points
    final pb = getPointBetween(px0, py0, px1, py1);
    final mx = pb.first;
    final my = pb.last;

    return Arrow(px0, py0, mx, my, px1, py1);
  }

  final downWards = y0 < y1;

  // ⤜⤏ Arrow is an arc!
  int rot;
  if (arcDirection == ArcDirection.Auto) {
    // Is the arc clockwise or counterclockwise?
    rot = (getSector(angle) % 2 == 0 ? 1 : -1) * (flip ? -1 : 1);
  } else if (arcDirection == ArcDirection.Left) {
    rot = downWards ? -1 : 1;
  } else {
    rot = downWards ? 1 : -1;
  }

  // Calculate how much the line should "bow" away from center
  final arc =
      bow + mod(dist, [stretchMin, stretchMax], [1, 0], clamp: true) * stretch;

  // Step 1 ⤜⤏ Find padded points.

  // Get midpoint.
  final mp = getPointBetween(x0, y0, x1, y1);

  final mx = mp.first;
  final my = mp.last;

  // Get control point.
  final cp = getPointBetween(x0, y0, x1, y1, d: 0.5 - arc);
  var cx = cp.first;
  var cy = cp.last;

  // Rotate control point (clockwise or counterclockwise).
  final rcp = rotatePoint(cx, cy, mx, my, (pi / 2) * rot);
  cx = rcp.first;
  cy = rcp.last;

  // Get padded start point.
  final a0 = getAngle(x0, y0, cx, cy);
  final psp = projectPoint(x0, y0, a0, padStart);
  final px0 = psp.first;
  final py0 = psp.last;

  // Get padded end point.
  final a1 = getAngle(x1, y1, cx, cy);
  final pep = projectPoint(x1, y1, a1, padEnd);
  final px1 = pep.first;
  final py1 = pep.last;

  // Step 3 ⤜⤏ Find control point for padded points.

  // Get midpoint between padded start / end points.
  final pmp = getPointBetween(px0, py0, px1, py1);
  final mx1 = pmp.first;
  final my1 = pmp.last;

  // Get control point for padded start / end points.
  final pcp = getPointBetween(px0, py0, px1, py1, d: 0.5 - arc);
  var cx1 = pcp.first;
  var cy1 = pcp.last;

  // Rotate control point (clockwise or counterclockwise).
  final rpcp = rotatePoint(cx1, cy1, mx1, my1, (pi / 2) * rot);
  cx1 = rpcp.first;
  cy1 = rcp.last;

  // Finally, average the two control points.
  final acp = getPointBetween(cx, cy, cx1, cy1);
  final cx2 = acp.first;
  final cy2 = acp.last;

  return Arrow(px0, py0, cx2, cy2, px1, py1);
}