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);
}