I recently created my first Svelte app to interactively explain how Bearing Rate Graphs work: https://harryli0088.github.io/bearing-rate-graph/

As part of the explanation, I wanted to make an unwrapping polar coordinate graph animation like Smarter Every Day had done in his video: https://youtu.be/AqqaYs7LjlM?t=447

I created this animation using

Svelte's motion

`tweened`

function, based off their tutorial: https://svelte.dev/tutorial/tweenedInterpolations using D3.js'

`scaleLinear`

: https://github.com/d3/d3-scaleSVG

`line`

and`path`

## How I implemented it

After looking closely at Smarter Every Day's animation, I realized that several steps are necessary to achieve the unwrapping effect:

Interpolate the line endpoints from the center of the polar circle to the bottom of the BRG rectangle

Interpolate the angle of the lines from polar to

straight up and downUse some trigonometry to calculate the positions of the edges of the polar circle to the top of the BRG rectangle

## Step 1

We can start by hardcoding the polar coordinate graph with a circle and some ticks at major angles

```
$: ticks = [ //hard code the tick positions
{angle:180, label: "180°", x1: 0, y1: halfHeight, x2: 0, y2: 0},
{angle:225, label: "", x1: -halfWidth/Math.sqrt(2), y1: -halfHeight/Math.sqrt(2), x2: 0, y2: 0},
{angle:270, label: "270°", x1: -halfWidth, y1: 0, x2: 0, y2: 0},
{angle:315, label: "", x1: -halfWidth/Math.sqrt(2), y1: halfHeight/Math.sqrt(2), x2: 0, y2: 0},
{angle:0, label: "0° (360°)", x1: 0, y1: -halfHeight, x2: 0, y2: 0},
{angle:45, label: "", x1: halfWidth/Math.sqrt(2), y1: -halfHeight/Math.sqrt(2), x2: 0, y2: 0},
{angle:90, label: "90°", x1: halfWidth, y1: 0, x2: 0, y2: 0},
{angle:135, label: "", x1: halfWidth/Math.sqrt(2), y1: halfHeight/Math.sqrt(2), x2: 0, y2: 0},
{angle:179.999, label: "", x1: 0, y1: halfHeight, x2: 0, y2: 0},
]
```

Full Step 1 Code: https://github.com/harryli0088/svelte-polar-animation-tutorial/blob/main/src/Step1.svelte

## Step 2

Next, we can introduce `tweened`

from Svelte's motion package, which automatically updates values in the DOM. We can also set an interval to periodically change the value of `animation`

.

```
import { onDestroy } from 'svelte'
import { tweened } from 'svelte/motion'
import { cubicOut } from 'svelte/easing'
const animation = tweened(0, {
duration: 4000,
easing: cubicOut
})
const interval = setInterval(() => {
animation.set($animation===1 ? 0 : 1)
}, 5000)
onDestroy(() => clearInterval(interval))
```

Full Step 2 Code: https://github.com/harryli0088/svelte-polar-animation-tutorial/blob/main/src/Step2.svelte

## Step 3

Next we need to interpolate the `line`

endpoints from the center of the polar circle to the bottom of the BRG rectangle.

### x2

The value of x2 changes of course depending on which angle we're looking at. In the animation, we can see that all angles start at the center of the circle. By the end of the animation, the angles (180° -> 360° or 0° -> 180°) end at x positions (left side -> center -> right side). We can represent this interpolation using D3.js' `scaleLinear`

like this

```
$: x2Scale = scaleLinear().domain(
[0, 180, 180, 360]
).range(
[
0,
$animation*halfWidth,
-$animation*halfWidth,
0,
]
)
```

### y2

y2 simple moves from the center of the circle to the bottom of the svg, for every line.

```
$: y2 = $animation * halfHeight
```

Full Step 3 Code: https://github.com/harryli0088/svelte-polar-animation-tutorial/blob/main/src/Step3.svelte

## Step 4

Next comes the trickiest part. We want our lines to unwrap and position themselves straight up and down. This means that the angles of all the lines transition from the starting angle to 0°. For example, the line with angle 270° starts at 270° and ends up as 0°; the line at 135° starts at 135° and finishes at 0°. In my code, I call this transitioning angle `theta`

. We just have to make sure that angles 180° and above transition towards 360°, and angles 180° and below transition towards 0°. (I accomplish the first part by subtracting those >=180° angles by 360°, so that 270° for example becomes -90°).

```
$: thetaScale = scaleLinear().domain(
[0, 180, 180, 360]
).range(
[0, (1 - $animation)*180, ($animation - 1)*180, 0]
)
```

Then we can take `theta`

and use trigonometry to calculate the `line`

endpoints `x1`

and `y1`

,

```
$: getLineDataFromAngle = (angle, radius=halfWidth) => {
const x2 = x2Scale(angle)
const theta = thetaScale(angle) / DEG_PER_RAD
return {
x1: x2 + radius * Math.sin(theta),
y1: - radius * Math.cos(theta),
x2, y2,
}
}
```

```
$: lineData = ticks.map(t => getLineDataFromAngle(t.angle))
```

Also, instead of having a `circle`

, which would not be feasible to animate in the way we need, we can fake a circle to line transition with SVG `path`

, like this:

```
const circleDegrees:number[] = []
for(let i=180; i<360; ++i) {
circleDegrees.push(i)
}
for(let i=0; i<180; ++i) {
circleDegrees.push(i)
}
circleDegrees.push(179.9) //this is now equal to [180, 181, ..., 359, 0, 1, ..., 179, 179.9]
$: topFactor = (12 + 3*$animation) / 16
$: circlePathRadius = halfWidth * topFactor
$: circlePath = circleDegrees.reduce(
(d, degree) => {
const { x1, y1 } = getLineDataFromAngle(degree, circlePathRadius)
d += ` ${x1},${y1}`
return d
},
"M"
)
```

Full Step 4 Code: https://github.com/harryli0088/svelte-polar-animation-tutorial/blob/main/src/Step4.svelte

## Final Result

Lastly we can add tweaks to the animation such as:

Add padding for the time axis

Transition in the time axis

Dy changes for the angle labels

## Source Code

Repo:

Final Svelte Component Code:

## Top comments (0)