Published
Edited
Feb 15, 2021
Importers
1 star
Insert cell
Insert cell
Insert cell
scrubber = TangledScrubber(Array.from({ length: 256 }, (_, i) => i), {
initialDelay: 200,
tMin: 10,
tMax: 500,
tMinWidth: "4em"
})
Insert cell
viewof temp = TangledScrubber(Array.from({ length: 256 }, (_, i) => i), {
initialDelay: 200,
tMin: 10,
tMax: 500,
tMinWidth: "4em"
})
Insert cell
temp
Insert cell
Insert cell
mutable n = null
Insert cell
Insert cell
function TangledScrubber(
values,
{
format = value => value,
initial = 0,
initialDelay = 100,
autoplay = true,
loop = true,
alternate = false,
tMin = -Infinity, // min delay
tMax = Infinity, // max delay
tMinWidth = undefined,
tStep = 1,
tPower = 1.2,
tFormat = x => `${x} ms`
} = {}
) {
let delay = initialDelay;
const tangle = html`<p style="margin-right:0.8em;">Delay: ${Tangle({
min: tMin,
max: tMax,
value: initialDelay,
minWidth: tMinWidth,
step: tStep,
power: tPower,
format: tFormat
})}</p>`;
values = Array.from(values);
const form = html`<form style="font: 12px var(--sans-serif); font-variant-numeric: tabular-nums; display: flex; height: 33px; align-items: center;">
${tangle}
<button name=b type=button style="margin-right: 0.4em; width: 5em;"></button>
<label style="display: flex; align-items: center;">
<input name=i type=range min=0 max=${values.length -
1} value=${initial} step=1 style="width: 180px;">
<output name=o style="margin-left: 0.4em;"></output>
</label>
</form>`;
let timer = null;
let direction = 1;
function start() {
form.b.textContent = "Pause";
timer =
delay === null ? requestAnimationFrame(tick) : setInterval(tick, delay);
}
function stop() {
form.b.textContent = "Play";
if (delay === null) cancelAnimationFrame(timer);
else clearInterval(timer);
timer = null;
}
function tick() {
if (delay === null) timer = requestAnimationFrame(tick);
if (
form.i.valueAsNumber ===
(direction > 0 ? values.length - 1 : direction < 0 ? 0 : NaN)
) {
if (!loop) return stop();
if (alternate) direction = -direction;
}
form.i.valueAsNumber =
(form.i.valueAsNumber + direction + values.length) % values.length;
form.i.dispatchEvent(new CustomEvent("input", { bubbles: true }));
}
form.i.oninput = event => {
if (event && event.isTrusted && timer) form.b.onclick();
form.value = {
delay,
value: values[form.i.valueAsNumber]
};
form.o.value = format(form.value.value, form.i.valueAsNumber, values);
};
form.b.onclick = () => {
if (timer) return stop();
direction =
alternate && form.i.valueAsNumber === values.length - 1 ? -1 : 1;
form.i.valueAsNumber = (form.i.valueAsNumber + direction) % values.length;
form.i.dispatchEvent(new CustomEvent("input", { bubbles: true }));
start();
};
function changeSpeed(e) {
delay = +e.target.value;
if (form.b.textContent == "Pause") {
stop();
start();
}
form.value.delay = delay;
}
tangle.getElementsByTagName("span")[0].addEventListener("input", changeSpeed);
tangle.getElementsByTagName("span").value = delay;
form.i.oninput();
if (autoplay) {
start();
} else {
stop();
}
disposal(form).then(stop);
return form;
}
Insert cell
import { Tangle } from "@mbostock/tangle"
Insert cell
import { disposal } from "@mbostock/disposal"
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