# Smooth a Svg path with cubic bezier curves

## 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> elementconst svgPath = (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},\${point}`    // 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 commandconst lineCommand = point => `L \${point} \${point}``

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 lineconst line = (pointA, pointB) => {  const lengthX = pointB - pointA  const lengthY = pointB - pointA  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 coordinatesconst controlPoint = (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 + Math.cos(angle) * length  const y = current + Math.sin(angle) * length  return [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 commandconst bezierCommand = (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},\${point}`}`

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):