Public
Edited
Dec 25, 2022
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// from https://github.com/chenglou/react-motion/blob/master/src/presets.js with undamped and overdamped added
oscillationRegimes = [
{name: "undamped", tension: 180, friction: 0},
{name: "underdamped", tension: 180, friction: 12},
{name: "overdamped", tension: 16, friction: 36},
{name: "critically damped", tension: 170, friction: 26},
]
Insert cell
Insert cell
Insert cell
\zeta = \texttt{damping}
Insert cell
\omega = \dfrac{\pi}{\texttt{overshootTime}\cdot\sqrt{1 - \texttt{damping}^2}}
Insert cell
# What About...

## Didn't X Already Do This?
Probably yes! The interface I'm proposing isn't new (in fact it's based on equations you'd find in an intro physics course that covered springs), but I would like to see it used more often.

## Overdamped systems?

An overdamped system is one where the springiness "percentage" is less than 0 (i.e. the damping ratio is greater than 1). It's really difficult to compute the settling time of an overdamped system, and they act very similarly to exponential decay systems. You might as well just use exponential decay directly, which has an easy-to-compute settling time and a simpler differential equation.

## Overshoot %?
This was proposed for React Motion: https://github.com/chenglou/react-motion/issues/265.
You have to choose two of overshoot %, duration, and damping. (You can add more factors as well, but you always have to choose two.) In my opinion, damping is more intuitive than overshoot % since it controls how "springy" the animation feels.

## Frequency Response?
https://medium.com/ios-os-x-development/demystifying-uikit-spring-animations-2bb868446773
Proposed by Apple. Frequency response is the frequency of the system without damping. This is *really* weird, because the "natural frequency" of the damped system is actually *different* from the frequency of the undamped system, so it is hard to map the frequency response to the output. I would argue that natural frequency is better, but this is just the inverse of the max overshoot time. Plus overshoot time makes sense for critically damped systems whereas the natural frequency of such a system is infinity.


## Settling Time for Underdamped?
Settling time is pretty hard to approximate well for underdamped systems. Also it's very sensitive to your chosen precision (is a spring settled when it's velocity is 0.1? 0.001? 0.0001?) whereas overshoots aren't.

## Natural Frequency?
This is essentially one over the damping ratio. **(TODO: check that...)**. This is more cumbersome, because common values are distributed weirdly. The damping ratio is more intuitive.

## First Time to Target?
Not as perceptible as max overshoot when looking at the resulting animation. It's really easy to tell by eye when the max overshoot happens. It's much harder to tell when the system first reaches its target. Max overshoot also nicely separates the animation into a "time-to-get-there" phase and the "settling" phase. Arguably this one does, too. TODO: how complicated is the computation?
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
mutable xReferenceDuration = leftEdge
Insert cell
mutable vReferenceDuration = 0
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
mutable xReference = leftEdge
Insert cell
mutable vReference = 0
Insert cell
padding = radius
Insert cell
leftBorder = padding
Insert cell
rightBorder = width - padding
Insert cell
paddedWidth = width - 2*padding
Insert cell
leftEdge = leftBorder + paddedWidth / 3
Insert cell
rightEdge = rightBorder - paddedWidth / 3
Insert cell
goRight = true
Insert cell
ZETA_MAX = -Math.log(precision)/Math.sqrt(Math.PI**2 + Math.log(precision)**2)
Insert cell
CRITICAL_APPROX = 6.63835
Insert cell
function convert({ overshootTime, damping }) {
let omega;
if (damping < ZETA_MAX) {
/* underdamped */
omega = Math.PI / (overshootTime * Math.sqrt(1 - damping ** 2));
} else {
/* critically damped */
/* above ZETA_MAX, system is indistinguishable from critically damped condition */
/* TODO: revisit the error rates using this threshold vs. not using it */
omega = CRITICAL_APPROX * damping / overshootTime;
}

return { tension: omega ** 2., friction: 2. * damping * omega };
};
Insert cell
/* TODO: this approximation DOES NOT work in the overdamped setting. */
function convertBack({ tension, friction }) {
let damping = friction / (2 * Math.sqrt(tension));

let overshootTime;
if (damping < ZETA_MAX) {
overshootTime = Math.PI / Math.sqrt(tension - friction**2/4)
} else {
overshootTime = CRITICAL_APPROX * friction / (2 * tension)
}

return {damping, overshootTime}
}
Insert cell
height = 33
Insert cell
radius = height / 2 - 5
Insert cell
import {Copier} from "@mbostock/copier"
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more