Paul Cowan

Nomadic cattle rustler and inventor of the electric lasso

Render Any Polygon in a React Component

I am writing this because I could not find anything adequate in my interweb searches for how to draw a hexagon using a svg polygon

What I wanted to do was create an svg hexagon in the guise of a react component. I wanted to supply a size property and the component would magically know how to create the coordinates that are be used for the points attribute of the hexagon.

Hexagon Maths

In the figure above, the angles a, b, c, d,e and f are the central angles of a hexagon. The number of central angles of a polygon is always the same as the number of sides.

As you can see above all the central angles of the polygon will always form a complete circle. Hence the central angles will always add up to 360∘. Since a regular polygon has all equal sides, then the central angles of the polygon are equal.

To find the central angles of a polygon:

  1. First identify the number of sides n.
  2. Then divide by 360∘.

The central angle of a hexagon is therefore 360 / 6 = 60∘.

If I create coordinates at 0, 60, 120, 180, 240 and 360 and all of the same length, then the <polygon /> component will join the vertices to create a hexagon.

I can use the trigonometry functions of cos and sin to set a coordinate if I know the central angle of the polygon and the length of each side of the polygon. cosine can give me the x coordinate and sine can give me the y coordinate.

But wait, we can do better

But why stop at a hexagon? The same maths should apply to any equal sided polygon. If I want to render an octagon then the same maths applies, all I need to do is 360 / 8 to get the central angle. If I want a pentagon, then I just need to 360 / 5 The number of sides could be a prop to the component.

Show me the code

Armed with this knowledge I can now work out the points by just knowing the central angle and the length of a side and an optional central point object that gives me a starting position to center the polygon.

getPoints.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export const getPoints = ({
  sides,
  size,
  center,
  rotate
}: {
  sides: number;
  size: number;
  center: Point;
  rotate: number;
}): Point[] => {
  return range(0, sides).map((n) => {
    const degrees = (360 / sides) * n - rotate;
    const radians = degreesToRadians(degrees);

    return new Point({
      x: center.x + size * Math.cos(radians),
      y: center.y + size * Math.sin(radians)
    });
  });
};

The function takes a sides argument which is the number of sides in the polygon, for example a hexagon has 6. The size or length of each side, an optional center argument which is the x and y coordinates of an optional offset to move the polygon and a rotate argument which will roate the polygon by a number of degrees, more on this laer.

Line 12 - const degrees = (360 / sides) * n - rotate; will get the central angle of the polygon which will be used to ascertain each point in the polygon.

Line 14 - const radians = (Math.PI / 180) * degrees; converts degrees into radians. Both Math.cos and Math.sin expect the number argument to be in radians and not degrees. I initially got this wrong and ended up with something very far removed from a hexagon.

The code below will work out the x and y coordinates of each of the six points.

ret.js
1
2
3
4
return new Point({
  x: center.x + size * Math.cos(radians),
  y: center.y + size * Math.sin(radians)
});

Here is a jsfiddle with a plain js version, you can just change the sides variable to create a different polygon.

Below is the react component in it’s entirety:

hexagon.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import * as React from 'react';
import { Point } from '../../model';
import { range } from 'lodash';
import { degreesToRadians } from '../../util/trigonometry';

export interface PolygonProps {
  sides: number;
  size: number;
  center?: Point;
  className?: string;
  rotate?: number;
}

export const getPoints = ({
  sides,
  size,
  center,
  rotate
}: {
  sides: number;
  size: number;
  center: Point;
  rotate: number;
}): Point[] => {
  return range(0, sides).map((n) => {
    const degrees = (360 / sides) * n - rotate;
    const radians = degreesToRadians(degrees);

    return new Point({
      x: center.x + size * Math.cos(radians),
      y: center.y + size * Math.sin(radians)
    });
  });
};

export const Polygon: React.SFC<PolygonProps> = ({
  sides,
  size = 25,
  center = new Point({ x: 0, y: 0 }),
  rotate = 0,
  className
}) => {
  const points = getPoints({ sides, size, center, rotate })
    .map((p) => p.toArray())
    .join(' ');

  return <polygon points={points} className={className} />;
};

The points are transformed into a string on line 47 which is what the polygon expects. Below is the rendered output:

polygon.html
1
<polygon points="21.65063509461097,-12.499999999999998 21.65063509461097,12.499999999999998 1.5308084989341915e-15,25 -21.65063509461097,12.499999999999998 -21.650635094610966,-12.500000000000004 -4.592425496802574e-15,-25"></polygon>

The infamous hexagon

I decided to make a more specialised Polygon component called Hexagon to satisfy my original need:

Hexagon.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
export enum HexagonType {
  flat = 'flat',
  pointy = 'pointy'
}

export interface HexagonProps extends Omit<PolygonProps, 'sides'> {
  size: number;
  hexagonType?: HexagonType;
  center?: Point;
  className?: string;
}

export const Hexagon: React.SFC<HexagonProps> = ({
  hexagonType = HexagonType.pointy,
  size = 25,
  center,
  className
}) => {
  const rotate = hexagonType === HexagonType.pointy ? 30 : 0;

  return <Polygon sides={6} size={size} rotate={rotate} center={center} className={className} />;
};

The component takes a HexagonType prop which is a typescript enum and on line 19, I create a turnHexagon variable that adds an rotate value (or not) to have the hexagon rotated. This rotate prop is passed to the Polygon component.

This means the hexagon can be pointy:

or flat

Comments