SVG Arrows in React

Creating SVG Arrows in React: A Step-by-Step Guide

The following is a simple svg with a path component, Lets see how we can use this and create point connectors with arrow heads. SHOW ME THE CODE...

html:index.html
1<svg
2 style="position: absolute; left: 100px; top: 100px;"
3 width="200px"
4 height="200px"
5>
6 <path
7 d="M <s.x>, <s.y> C <cp1.x> <cp1.y> <cp2.x> <cp2.y> <e.x>, <e.y>"
8 stroke="#007AB8"
9 fill="transparent"
10 strokeWidth="2px"
11 />
12</svg>

Here's how the path is depicted.

  1. Move to starting point (s.x, s.y)
  2. Draw the bezier curve to ending point (e.x, e.y)
  3. With 2 control points cp1 and cp2.
1d="M s.x, s.y C cp1.x cp1.y cp2.x cp2.y e.x, e.y"
2<!--d="Move to (s.x, s.y) Control points (cp1.x, cp1.y) and (cp2.x, cp2.y) end point at (e.x, e.y)" -->

I am a visual person.

hmmm... sure... here you go..

Drag the points to see the curve
<path d="M 100, 100 C 300 100 100 300 300, 300" /> 
StartEndControl Point 1Control Point 2

Ok... Lets Begin with some basic structure..

create a point interface and an implementation with a method to move the point.

tsx:arrow/index.tsx
1export interface IPoint {
2 x: number;
3 y: number;
4}
5class Point implements IPoint {
6 x: number = 0;
7 y: number = 0;
8 constructor({ x, y }: IPoint) {
9 this.add({ x, y });
10 }
11 /*This will move the point by incoming vector p*/
12 add(p: IPoint) {
13 this.x += p.x;
14 this.y += p.y;
15 }
16}

We need some helper..

Lets create some helper functions for direction of the curve. This will check if direction is left or right or up or down

tsx:arrow/index.tsx
1export enum Direction {
2 Left,
3 Right,
4 Up,
5 Down,
6}
7
8function isLeftRight(dir: Direction) {
9 return dir === Direction.Left || dir === Direction.Right;
10}
11
12function isUpDown(dir: Direction) {
13 return dir === Direction.Up || dir === Direction.Down;
14}

Control Points

Lets create a function to get control points for given start and end points and a Direction

tsx:arrow/index.tsx
1function getCP(s: IPoint, e: IPoint, dir: Direction) {
2 const cpTC = {
3 x: e.x,
4 y: s.y,
5 };
6
7 const cpBC = {
8 x: s.x,
9 y: e.y,
10 };
11
12 const cpLC = {
13 x: s.x,
14 y: e.y,
15 };
16
17 const cpRC = {
18 x: e.x,
19 y: s.y,
20 };
21
22 if (isLeftRight(dir)) return { cp1: cpTC, cp2: cpBC };
23 if (isUpDown(dir)) return { cp1: cpLC, cp2: cpRC };
24 return { cp1: cpTC, cp2: cpBC };
25}

How about a simple Arrow Head

Sure thing.. lets create a function to get Arrow Head points

tsx:arrow/index.tsx
1
2function getArrow(s: IPoint, e: IPoint, d: number, dir: Direction) {
3 if (isUpDown(dir)) {
4 const l = d * (s.y < e.y ? 2 : -2);
5 return {
6 p1: { x: e.x - d, y: e.y - l },
7 p2: { x: e.x + d, y: e.y - l },
8 };
9 } else {
10 const l = d * (s.x < e.x ? 2 : -2);
11 return {
12 p1: { x: e.x - l, y: e.y - d },
13 p2: { x: e.x - l, y: e.y + d },
14 };
15 }
16}

Finally our main Arrow component

tsx:arrow/Arrow.tsx
1export default function Arrow({
2 from,
3 to,
4 dir,
5 className,
6}: {
7 from: IPoint;
8 to: IPoint;
9 dir: Direction;
10 className?: string;
11}) {
12 const offSet = 10;
13 const s = new Point({
14 x: from.x + offSet,
15 y: from.y + offSet,
16 });
17 const e = new Point({
18 x: to.x + offSet,
19 y: to.y + offSet,
20 });
21
22 const offsetPoint = {
23 x: -Math.min(from.x, to.x),
24 y: -Math.min(from.y, to.y),
25 };
26
27 s.add(offsetPoint);
28 e.add(offsetPoint);
29
30 const { cp1, cp2 } = getCP(s, e, dir)!;
31 const { p1, p2 } = getArrow(s, e, offSet / 2, dir)!;
32
33 return (
34 <svg
35 className={className}
36 width={Math.abs(to.x - from.x) + 2 * offSet}
37 height={Math.abs(to.y - from.y) + 2 * offSet}
38 >
39 <!--Draw bezier curve-->
40 <path
41 d={`M ${s.x}, ${s.y} C ${cp1.x} ${cp1.y} ${cp2.x} ${cp2.y} ${e.x}, ${e.y}`}
42 stroke='#007AB8'
43 fill='transparent'
44 strokeWidth={2}
45 />
46
47 <!--Draw arrow head-->
48 <polygon
49 points={`${e.x}, ${e.y}, ${p1.x}, ${p1.y}, ${p2.x}, ${p2.y} `}
50 strokeWidth={1}
51 stroke='#007AB8'
52 fill='#007AB8'
53 strokeLinejoin={'round'}
54 />
55 </svg>
56 );
57}

Lets see them in action.

tsx:arrow/Arrows.tsx
1 <Arrow
2 from={{ x: 310, y: 220 }}
3 to={{ x: 420, y: 420 }}
4 dir={Direction.Left}
5 />
6
7 <Arrow
8 from={{ x: 920, y: 220 }}
9 to={{ x: 820, y: 420 }}
10 dir={Direction.Right}
11 />
12
13 <Arrow
14 from={{ x: 920, y: 220 }}
15 to={{ x: 820, y: 420 }}
16 dir={Direction.Up}
17 />
18
19 <Arrow
20 from={{ x: 920, y: 220 }}
21 to={{ x: 820, y: 420 }}
22 dir={Direction.Down}
23 />

Conclusion

Hopefully you've gained valuable insights by working with SVG and employing React to control the coordinates.