Skip to content

Instantly share code, notes, and snippets.

@gaearon
Last active October 26, 2021 14:14
Show Gist options
  • Save gaearon/8b516449acc73bc75ac977000f89efc1 to your computer and use it in GitHub Desktop.
Save gaearon/8b516449acc73bc75ac977000f89efc1 to your computer and use it in GitHub Desktop.
Curved SVG arrow between two objects (rects or circles) https://twitter.com/dan_abramov/status/1362255543721672704
// from/to: { left, top, width, height, shape: 'circle' | 'rect' }
function CurvedArrow({ from, to }) {
function curvedHorizontal(x1, y1, x2, y2) {
function pos(t) {
let mx = x1 + (x2 - x1) / 2;
let p1 = {x: x1, y: y1};
let p2 = {x: mx, y: y1};
let p3 = {x: mx, y: y2};
let p4 = {x: x2, y: y2};
return {
x: (
((1 - t) ** 3) * p1.x +
3 * ((1 - t) ** 2) * t * p2.x +
3 * (1 - t) * (t ** 2) * p3.x +
(t ** 3) * p4.x
),
y: (
((1 - t) ** 3) * p1.y +
3 * ((1 - t) ** 2) * t * p2.y +
3 * (1 - t) * (t ** 2) * p3.y +
(t ** 3) * p4.y
)
};
}
function intersects(point, area) {
if (area.shape === 'rect') {
return (
point.x >= area.left &&
point.x <= (area.left + area.width)
) && (
point.y >= area.top &&
point.y <= (area.top + area.height)
);
} else if (area.shape === 'circle') {
const center = {
x: area.left + (area.width / 2),
y: area.top + (area.height / 2)
}
return Math.sqrt(
(center.x - point.x) ** 2 +
(center.y - point.y) ** 2
) <= area.width / 2;
}
}
let line = []
for (let t = 0; t < 1; t += 0.001) {
let p = pos(t)
if (!intersects(p, from) && !intersects(p, to)) {
line.push(p.x, p.y);
}
}
return line.length > 0 ? 'M ' + line.join(' ') : '';
}
return (
<path d={curvedHorizontal(
from.left + from.width / 2,
from.top + from.height / 2,
to.left + to.width / 2,
to.top + to.height / 2,
)}
stroke="black"
strokeWidth={4}
fill="none"
markerEnd="url(#arrowhead)"
/>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment