UNPKG

@tanstack/charts

Version:

A chart grammar for TypeScript and JavaScript. Marks consume your data directly, channels describe visual encodings, and the engine compiles them into a renderer-neutral keyed scene. TanStack's compact scales cover common numeric and categorical mappings.

89 lines (88 loc) 3.69 kB
const defaultSpringOptions = { stiffness: 170, damping: 26, mass: 1, restSpeed: 0.01, restDelta: 5e-3 }; const defaultState = { from: 0, to: 1, velocity: 0 }; function createChartSpring(input = {}) { const options = Object.freeze(resolveSpringOptions(input)); const sample = (elapsedMs, state = defaultState) => { if (elapsedMs === Number.POSITIVE_INFINITY) { return { value: finite(state.to, 1), velocity: 0, done: true }; } const elapsed = Number.isFinite(elapsedMs) ? Math.max(0, elapsedMs) : 0; return sampleSpring(options, elapsed / 1e3, state); }; return { options, sample }; } function sampleSpring(options, elapsed, state) { const from = finite(state.from, 0); const to = finite(state.to, 1); const initialVelocity = finite(state.velocity, 0); const displacement = from - to; if (Math.abs(displacement) <= options.restDelta && Math.abs(initialVelocity) <= options.restSpeed) { return { value: to, velocity: 0, done: true }; } const omega = Math.sqrt(options.stiffness / options.mass); const dampingRatio = options.damping / (2 * Math.sqrt(options.stiffness * options.mass)); let offset; let velocity; if (dampingRatio < 1 - 1e-7) { const decay = dampingRatio * omega; const frequency = omega * Math.sqrt(1 - dampingRatio * dampingRatio); const secondary = (initialVelocity + decay * displacement) / frequency; const cosine = Math.cos(frequency * elapsed); const sine = Math.sin(frequency * elapsed); const envelope = Math.exp(-decay * elapsed); offset = envelope * (displacement * cosine + secondary * sine); velocity = envelope * ((secondary * frequency - decay * displacement) * cosine + (-displacement * frequency - decay * secondary) * sine); } else if (dampingRatio > 1 + 1e-7) { const root = Math.sqrt(dampingRatio * dampingRatio - 1); const slow = -omega / (dampingRatio + root); const fast = -omega * (dampingRatio + root); const slowWeight = (initialVelocity - fast * displacement) / (slow - fast); const fastWeight = displacement - slowWeight; const slowEnvelope = Math.exp(slow * elapsed); const fastEnvelope = Math.exp(fast * elapsed); offset = slowWeight * slowEnvelope + fastWeight * fastEnvelope; velocity = slow * slowWeight * slowEnvelope + fast * fastWeight * fastEnvelope; } else { const secondary = initialVelocity + omega * displacement; const envelope = Math.exp(-omega * elapsed); offset = (displacement + secondary * elapsed) * envelope; velocity = (secondary - omega * (displacement + secondary * elapsed)) * envelope; } if (!Number.isFinite(offset) || !Number.isFinite(velocity)) { return { value: to, velocity: 0, done: true }; } const done = Math.abs(offset) <= options.restDelta && Math.abs(velocity) <= options.restSpeed; return { value: done ? to : to + offset, velocity: done ? 0 : velocity, done }; } function resolveSpringOptions(input) { return { stiffness: positive(input.stiffness, defaultSpringOptions.stiffness), damping: nonNegative(input.damping, defaultSpringOptions.damping), mass: positive(input.mass, defaultSpringOptions.mass), restSpeed: positive(input.restSpeed, defaultSpringOptions.restSpeed), restDelta: positive(input.restDelta, defaultSpringOptions.restDelta) }; } function finite(value, fallback) { return value !== void 0 && Number.isFinite(value) ? value : fallback; } function positive(value, fallback) { const resolved = finite(value, fallback); return resolved > 0 ? resolved : fallback; } function nonNegative(value, fallback) { return Math.max(0, finite(value, fallback)); } export { createChartSpring };