# Smooth a Svg path with cubic bezier curves

## And a bit of trigonometry

--

## While it is straightforward to draw straight lines in a Svg element, it requires a bit of trigonometry to smooth these lines. Let’s see how.

We have an array of tuples representing the points coordinates of a line.

`const `**points** = [[5, 10], [10, 40], [40, 30], [60, 5], [90, 45], [120, 10], [150, 45], [200, 10]]

And a Svg element in an HTML page:

`<`**svg** viewBox="0 0 200 200" version="1.1" xmlns="http://www.w3.org/2000/svg" class="svg"></svg>

We want to make a `<path>`

element from the `points`

array.

# Create a path from the points

The `d`

attributes of `<path>`

always starts with a *move to* command: `M x,y`

, followed by several commands depending on the type of shape. The result is something like: `<path d="M 10,20 L 15,25 L 20,35">`

for a straight line.

First, let’s make a generic `svgPath`

function which has two parameters: the `points`

array and a `command`

function.

//Render the svg <path> element

//I: - points (array): points coordinates

// - command (function)

//I: - point (array) [x,y]: current point coordinates

// - i (integer): index of 'point' in the array 'a'

// - a (array): complete array of points coordinates

//O: - (string) a svg path command

//O: - (string): a Svg <path> elementconstsvgPath= (points, command)=> { // build the d attributes by looping over the points

const d =points.reduce((acc, point, i, a) => i === 0 // if first point

? `M ${point[0]},${point[1]}` // else

: `${acc} ${command(point, i, a)}`

, '')return `<path d="${d}" fill="none" stroke="grey" />`

}

Now, let’s create two commands functions:

`lineCommand`

: to draw straight lines.`bezierCommand`

: to draw a smooth line.

# Drawing straight lines

Straight lines require the *line to* command, starting with the letter `L`

followed by the coordinates of the end point `x,y`

.

A basic `lineCommand`

function to draw straight lines:

//Svg path line command

//I: - point (array) [x, y]: coordinates

//O: - (string) 'L x,y': svg line commandconstlineCommand= point => `L ${point[0]} ${point[1]}`

Now we can use it to draw a line from the `points`

array:

`const svg = document.querySelector('.svg')`

svg.innerHTML = **svgPath**(points, **lineCommand**)

**This gives the following result (****view on Codepen****):**

# Drawing smooth lines

## The cubic bezier command

The *cubic bezier* command starts with the letter `C`

followed by three pairs of coordinates `x1,y1 x2,y2 x,y`

:

`x1,y1`

: coordinates of the start control point`x2,y2`

: coordinates of the end control point`x,y`

: coordinates of the end anchor point

A few things to notice:

- The start anchor point coordinates are given by the previous command.
- The end anchor point coordinates come from the original
`points`

array. - Now we have to find the position of the two control points.

## Find the position of the control points

We join the anchor points surrounding the start and the end anchor points with a line (let’s call these the `opposed-line`

s):

For the line to be smooth, the position of each control point has to be relative to its `opposed-line`

:

- The control point is on a
*line*parallel to the`opposed-line`

, and tangent to the current anchor point. - On this tangent line, the
*distance*from the anchor point to the control point depends on the length of the`opposed-line`

and an arbitrary`smoothing`

ratio. - The start control point goes in the same
*direction*as the`opposed-line`

, while the end control point goes backward.

## Code

First, a function to find the properties of the `opposed-line`

:

//Properties of a line

//I: - pointA (array) [x,y]: coordinates

// - pointB (array) [x,y]: coordinates

//O: - (object) { length: l, angle: a }: properties of the lineconstline= (pointA,pointB) => {

const lengthX = pointB[0] - pointA[0]

const lengthY = pointB[1] - pointA[1]return {

length: Math.sqrt(Math.pow(lengthX, 2) + Math.pow(lengthY, 2)),

angle: Math.atan2(lengthY, lengthX)

}

}

Then, a function to find the position of a control point:

//Position of a control point

//I: - current (array) [x, y]: current point coordinates

// - previous (array) [x, y]: previous point coordinates

// - next (array) [x, y]: next point coordinates

// - reverse (boolean, optional): sets the direction

//O: - (array) [x,y]: a tuple of coordinatesconstcontrolPoint= (current,previous,next,reverse) => { // When 'current' is the first or last point of the array

// 'previous' or 'next' don't exist.

// Replace with 'current'

const p = previous || current

const n = next || current // The smoothing ratio

const smoothing = 0.2 // Properties of the opposed-line

const o =line(p, n) // If is end-control-point, add PI to the angle to go backward

const angle = o.angle + (reverse ? Math.PI : 0)

const length = o.length * smoothing // The control point position is relative to the current point

const x = current[0] + Math.cos(angle) * length

const y = current[1] + Math.sin(angle) * lengthreturn [x, y]

}

A function to create the bezier curve `C`

command:

//Create the bezier curve command

//I: - point (array) [x,y]: current point coordinates

// - i (integer): index of 'point' in the array 'a'

// - a (array): complete array of points coordinates

//O: - (string) 'C x2,y2 x1,y1 x,y': SVG cubic bezier C commandconstbezierCommand= (point,i,a) => { // start control point

const [cpsX, cpsY] =controlPoint(a[i - 1], a[i - 2], point) // end control point

const [cpeX, cpeY] =controlPoint(point, a[i - 1], a[i + 1], true)return `C ${cpsX},${cpsY} ${cpeX},${cpeY} ${point[0]},${point[1]}`

}

And finally we reuse the `svgPath`

function to loop over the `points`

of the array and build the `<path>`

element. Then we append the `<path>`

to the `<svg>`

element.

`const svg = document.querySelector('.svg')`

svg.innerHTML = **svgPath**(points, **bezierCommand**)

**And the result (****view on Codepen****):**

# Interesting links

- Cubic Bezier Curves, Under the Hood: a great animated explanation on how computers actually render bezier curves by Peter Nowell.
- D3.js curves functions documentation.