## DEV Community is a community of 620,183 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

# Unwrapping Polar Coordinate Graph Animation with Svelte, D3.js, and SVG

Harry Li Updated on ・4 min read

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

## 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 down

• Use 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},
]
``````

## 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))
``````

## 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
``````

## 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),
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"
)
``````

## Final Result

Lastly we can add tweaks to the animation such as:

• Transition in the time axis

• Dy changes for the angle labels

Repo:

## harryli0088 / svelte-polar-animation-tutorial

Final Svelte Component Code: