Public
Edited
Jul 9, 2023
Importers
Insert cell
Insert cell
viewof inp = Inputs.radio(["a", "b", "c"], { value: "a" })
Insert cell
Insert cell
transitionGenerator(viewof inp)
Insert cell
Insert cell
trans
Insert cell
Insert cell
viewof trans = Inputs.input([])
Insert cell
transitionFilter(transitionGenerator(viewof inp), viewof trans)
Insert cell
Insert cell
Insert cell
Insert cell
position = ({ a: [1, 2, 3], b: [4, 5, 6], c: [7, 8, 9] })
Insert cell
Insert cell
transitionInterpolate(trans, (state) => position[state])
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// View, Object -> (() -> Object)*
transitionGenerator = function* (
view,
{ time = 1000, easing = Easing.Linear.None, states = getStates(view) } = {}
) {
if (!view.value) {
throw "Input must have a value set.";
}

const getWeights = makeGetWeights(states);
const getTransition = makeGetTransition(states);

// initalize tween
let start = getWeights(view.value);
let tween = new TWEEN.Tween(start);

// listen to input, invoke tween when it changes
const inputted = () => {
tween
.stop()
.to(getWeights(view.value), time)
.easing(easing)
.start(undefined, true);
};
view.addEventListener("input", inputted);

while (true) {
// `start` is mutable, updated by the tween
tween.update();
yield getTransition(start);
}

// when generator disposed, remove listener
return () => view.removeEventListener("input", inputted);
}
Insert cell
Insert cell
transitionFilter = function* (generator, view) {
while (true) {
const value = generator.next().value;
if (!_.isEqual(view.value, value)) {
// see https://observablehq.com/@observablehq/synchronized-inputs
view.value = value;
view.dispatchEvent(new Event("input", { bubbles: true }));
}
yield value;
}

return null;
}
Insert cell
Insert cell
transitionInterpolate = function (transition, callbackFn) {
return transition
.filter((x) => x.weight > 0)
.map((x) => math.multiply(callbackFn(x.state), x.weight))
.reduce((acc, val) => math.add(acc, val));
}
Insert cell
Insert cell
Easing = TWEEN.Easing
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
states = ["alpha", "bravo", "charlie"]
Insert cell
Insert cell
getWeights = makeGetWeights(states)
Insert cell
weights = getWeights("bravo")
Insert cell
Insert cell
Insert cell
Insert cell
getTransition = makeGetTransition(states)
Insert cell
getTransition([0, 0.5, 0.5])
Insert cell
Insert cell
// String[] -> (Number[] -> Object[])
makeGetTransition = function (states) {
// Number[] -> Object[]
return function (weights) {
return weights.map((x, i) => ({ state: states[i], weight: x }));
};
}
Insert cell
Insert cell
viewof inpt = Inputs.radio(states)
Insert cell
getStates(viewof inpt)
Insert cell
// Input -> String[]
getStates = function (input) {
// seems potentially brittle - is there a more-robust way?
return Array.from(input.children[0].children).map((x) => x.innerText);
}
Insert cell
Insert cell
TWEEN = import(
"https://unpkg.com/@tweenjs/tween.js@20.0.3/dist/tween.esm.js?module"
)
Insert cell
math = import("https://cdn.skypack.dev/mathjs@11.8.2?")
Insert cell
import { changeTable } from "@ijlyttle/change-log"
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