Skip to content

Perfect Arrows

@rougher-stuff/perfect-arrows computes arrow bodies, bounds, and endpoint decorations without coupling them to a rendering technology.

Point to point

ts
import { createArrow, toSvgPath } from '@rougher-stuff/perfect-arrows';

const arrow = createArrow({
  from: { x: 20, y: 40 },
  to: { x: 240, y: 160 },
  route: { type: 'swoop', bend: 0.4 },
  end: { type: 'open-arrow', size: 12 },
});

const pathData = toSvgPath(arrow.body);

Use createArrowWithoutBounds() when only the path and decorations are needed.

Between rectangles

Oriented boxes preserve their local coordinate systems rather than collapsing to axis-aligned bounds:

ts
import { createArrowBetweenOrientedRects } from '@rougher-stuff/perfect-arrows';

const arrow = createArrowBetweenOrientedRects({
  from: { center: { x: 60, y: 30 }, width: 120, height: 60, rotation: Math.PI / 4 },
  to: { center: { x: 300, y: 180 }, width: 80, height: 50, rotation: -Math.PI / 6 },
});

Routes and decorations

Routes include straight, arc, swoop, s-curve, elbow, wave, and pigtail.

Endpoint decorations include open-arrow, triangle, needle, dot, circle, and bar. Routing hints can set start and end directions when a route must leave or enter a particular side.

Legacy compatibility

The original getArrow() and getBoxToBoxArrow() functions remain available from the package root and from @rougher-stuff/perfect-arrows/legacy.

See the package README for the complete legacy API.

Released under the MIT License.