Skip to content

Commit

Permalink
fix animation jitters (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
venkatesh-sivaraman authored Aug 4, 2024
1 parent 326c73a commit 841ed67
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 66 deletions.
88 changes: 47 additions & 41 deletions counterpoint/dist/counterpoint-vis.es.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ class b {
* @param newValue The new value or value function.
*/
set(t) {
typeof t == "function" ? (this.value != null && (this._computedValue = this.value), this.valueFn = t, this.value = void 0, this._animatedValue = null) : (this.value = t, this.valueFn = null, this._animatedValue = null), this.needsUpdate = !0, this._lastTickValue = void 0, this.animation && this._cleanUpAnimation(!0), this._listeners.forEach((e) => e(this, !1));
typeof t == "function" ? (this.value != null && (this._computedValue = this.getUntransformed()), this.valueFn = t, this.value = void 0, this._animatedValue = null) : (this.value = t, this.valueFn = null, this._animatedValue = null), this.needsUpdate = !0, this._lastTickValue = void 0, this.animation && this._cleanUpAnimation(!0), this._listeners.forEach((e) => e(this, !1));
}
/**
* Retrieves the non-animated value for the attribute, i.e. the final value
Expand All @@ -239,7 +239,7 @@ class b {
* function.
*/
last() {
return this.animation && this._computeAnimation(!1), this._lastTickValue !== void 0 ? this._lastTickValue : this._animatedValue != null ? this._animatedValue : this.value !== void 0 ? this.value : this._computedValue;
return this.animation && this._preload && this._computeAnimation(!1), this._lastTickValue !== void 0 ? this._lastTickValue : this._animatedValue != null ? this._animatedValue : this.value !== void 0 ? this.value : this._computedValue;
}
/**
* Returns the value that this attribute is approaching if animating (or `null`
Expand Down Expand Up @@ -874,19 +874,19 @@ var et = new f({
}
}
});
const re = 25 ** 7, kt = Math.PI, ae = 180 / kt, H = kt / 180;
const re = 25 ** 7, kt = Math.PI, ae = 180 / kt, V = kt / 180;
function $t(i, t, { kL: e = 1, kC: r = 1, kH: a = 1 } = {}) {
let [s, n, o] = T.from(i), h = et.from(T, [s, n, o])[1], [l, u, c] = T.from(t), d = et.from(T, [l, u, c])[1];
h < 0 && (h = 0), d < 0 && (d = 0);
let p = ((h + d) / 2) ** 7, y = 0.5 * (1 - Math.sqrt(p / (p + re))), M = (1 + y) * n, k = (1 + y) * u, w = Math.sqrt(M ** 2 + o ** 2), D = Math.sqrt(k ** 2 + c ** 2), x = M === 0 && o === 0 ? 0 : Math.atan2(o, M), P = k === 0 && c === 0 ? 0 : Math.atan2(c, k);
x < 0 && (x += 2 * kt), P < 0 && (P += 2 * kt), x *= ae, P *= ae;
let q = l - s, Y = D - w, B = P - x, N = x + P, Wt = Math.abs(B), J;
w * D === 0 ? J = 0 : Wt <= 180 ? J = B : B > 180 ? J = B - 360 : B < -180 ? J = B + 360 : console.log("the unthinkable has happened");
let Nt = 2 * Math.sqrt(D * w) * Math.sin(J * H / 2), ui = (s + l) / 2, At = (w + D) / 2, Jt = Math.pow(At, 7), O;
let Nt = 2 * Math.sqrt(D * w) * Math.sin(J * V / 2), ui = (s + l) / 2, At = (w + D) / 2, Jt = Math.pow(At, 7), O;
w * D === 0 ? O = N : Wt <= 180 ? O = N / 2 : N < 360 ? O = (N + 360) / 2 : O = (N - 360) / 2;
let Qt = (ui - 50) ** 2, ci = 1 + 0.015 * Qt / Math.sqrt(20 + Qt), Kt = 1 + 0.045 * At, Q = 1;
Q -= 0.17 * Math.cos((O - 30) * H), Q += 0.24 * Math.cos(2 * O * H), Q += 0.32 * Math.cos((3 * O + 6) * H), Q -= 0.2 * Math.cos((4 * O - 63) * H);
let te = 1 + 0.015 * At * Q, di = 30 * Math.exp(-1 * ((O - 275) / 25) ** 2), fi = 2 * Math.sqrt(Jt / (Jt + re)), mi = -1 * Math.sin(2 * di * H) * fi, ot = (q / (e * ci)) ** 2;
Q -= 0.17 * Math.cos((O - 30) * V), Q += 0.24 * Math.cos(2 * O * V), Q += 0.32 * Math.cos((3 * O + 6) * V), Q -= 0.2 * Math.cos((4 * O - 63) * V);
let te = 1 + 0.015 * At * Q, di = 30 * Math.exp(-1 * ((O - 275) / 25) ** 2), fi = 2 * Math.sqrt(Jt / (Jt + re)), mi = -1 * Math.sin(2 * di * V) * fi, ot = (q / (e * ci)) ** 2;
return ot += (Y / (r * Kt)) ** 2, ot += (Nt / (a * te)) ** 2, ot += mi * (Y / (r * Kt)) * (Nt / (a * te)), Math.sqrt(ot);
}
const Ti = 75e-6;
Expand Down Expand Up @@ -1334,17 +1334,17 @@ const $i = 0.56, Xi = 0.57, ji = 0.62, Ii = 0.65, le = 0.022, Ui = 1.414, qi = 0
function ce(i) {
return i >= le ? i : i + (le - i) ** Ui;
}
function W(i) {
function H(i) {
let t = i < 0 ? -1 : 1, e = Math.abs(i);
return t * Math.pow(e, 2.4);
}
function Hi(i, t) {
t = g(t), i = g(i);
let e, r, a, s, n, o;
t = A(t, "srgb"), [s, n, o] = t.coords;
let h = W(s) * 0.2126729 + W(n) * 0.7151522 + W(o) * 0.072175;
let h = H(s) * 0.2126729 + H(n) * 0.7151522 + H(o) * 0.072175;
i = A(i, "srgb"), [s, n, o] = i.coords;
let l = W(s) * 0.2126729 + W(n) * 0.7151522 + W(o) * 0.072175, u = ce(h), c = ce(l), d = c > u;
let l = H(s) * 0.2126729 + H(n) * 0.7151522 + H(o) * 0.072175, u = ce(h), c = ce(l), d = c > u;
return Math.abs(c - u) < Zi ? r = 0 : d ? (e = c ** $i - u ** Xi, r = e * Gi) : (e = c ** Ii - u ** ji, r = e * Vi), Math.abs(r) < qi ? a = 0 : r > 0 ? a = r - ue : a = r + ue, a * 100;
}
function Wi(i, t) {
Expand Down Expand Up @@ -2564,7 +2564,7 @@ function Ht(i) {
return Jr;
return (t, e, r, a) => e < 1 ? t : r;
}
function V(i, t = void 0) {
function W(i, t = void 0) {
return t === void 0 && (t = Ht(i)), {
finalValue: i,
interpolate: (e, r) => t(
Expand All @@ -2575,7 +2575,7 @@ function V(i, t = void 0) {
)
};
}
function ha(i, t = void 0) {
function Qr(i, t = void 0) {
return t === void 0 && (t = Ht(i())), {
interpolate: (e, r) => t(
e,
Expand Down Expand Up @@ -2615,10 +2615,10 @@ class z {
return this.interpolator.interpolate(t, r);
}
withDelay(t) {
return t ? new Qr(this, t) : this;
return t ? new Kr(this, t) : this;
}
}
class Qr extends z {
class Kr extends z {
constructor(t, e) {
super(t.interpolator, t.duration + e, t.curve), this.delay = e;
}
Expand All @@ -2630,10 +2630,10 @@ class Qr extends z {
}
}
function ua(i, t = 1e3, e = oi) {
return new z(V(i), t, e);
return new z(W(i), t, e);
}
var Kr = /* @__PURE__ */ ((i) => (i.Waiting = "waiting", i.Entering = "entering", i.Visible = "visible", i.Exiting = "exiting", i.Completed = "completed", i))(Kr || {}), ta = /* @__PURE__ */ ((i) => (i.Show = "show", i.Hide = "hide", i))(ta || {});
class ea {
var ta = /* @__PURE__ */ ((i) => (i.Waiting = "waiting", i.Entering = "entering", i.Visible = "visible", i.Exiting = "exiting", i.Completed = "completed", i))(ta || {}), ea = /* @__PURE__ */ ((i) => (i.Show = "show", i.Hide = "hide", i))(ea || {});
class ia {
constructor(t = {}) {
this.markStates = /* @__PURE__ */ new Map(), this.marksByID = /* @__PURE__ */ new Map(), this.queuedAnimations = /* @__PURE__ */ new Map(), this._flushTimer = null, this.animatingMarks = /* @__PURE__ */ new Set(), this._updated = !1, this.defer = !1, this.saveExitedMarks = !1, this._callbacks = {
initialize: t.initialize || (() => {
Expand Down Expand Up @@ -2884,7 +2884,7 @@ function Pe(i, t, e) {
])
);
}
class ia {
class ra {
/**
* @param marks The set of marks that this group should manage, all including
* the same set of attributes.
Expand All @@ -2900,7 +2900,7 @@ class ia {
return;
}
this.marksByID.set(r.id, r), this.markSet.add(r), this._setupMark(r);
}), this.stage && this.stage.setVisibleMarks(this.marks);
}), this._setupStage();
}
/**
* Applies configuration options to the render group.
Expand All @@ -2920,13 +2920,19 @@ class ia {
* @returns this render group
*/
configure(t) {
return t.timeProvider !== void 0 && (this.timeProvider = t.timeProvider), t.lazyUpdates !== void 0 && (this.lazyUpdates = t.lazyUpdates), t.animationDuration !== void 0 && (this._defaultDuration = t.animationDuration), t.animationCurve !== void 0 && (this._defaultCurve = t.animationCurve), t.hitTest !== void 0 && (this._hitTest = t.hitTest), this.marks && this.getMarks().forEach((e) => this._configureMark(e)), this.useStaging = t.useStaging ?? this.useStaging, this.useStaging ? (this.stage = new ea(), this.marks && this.stage.setVisibleMarks(this.getMarks())) : this.stage = null, this;
return t.timeProvider !== void 0 && (this.timeProvider = t.timeProvider), t.lazyUpdates !== void 0 && (this.lazyUpdates = t.lazyUpdates), t.animationDuration !== void 0 && (this._defaultDuration = t.animationDuration), t.animationCurve !== void 0 && (this._defaultCurve = t.animationCurve), t.hitTest !== void 0 && (this._hitTest = t.hitTest), this.marks && this.getMarks().forEach((e) => this._configureMark(e)), this.useStaging = t.useStaging ?? this.useStaging, this._setupStage(), this;
}
configureStaging(t, e = void 0) {
return this.useStaging || console.error(
"Can't configure staging without setting useStaging to true"
), this.stage.onInitialize(t.initialize), this.stage.onEnter(t.enter), this.stage.onExit(t.exit), e && this.stage.configure(e), this;
}
/**
* Sets up the stage manager if it has not already been set up.
*/
_setupStage() {
this.useStaging ? (this.stage || (this.stage = new ia()), this.marks && this.stage.setVisibleMarks(this.getMarks())) : this.stage = null;
}
/**
* Sets up a mark for the first time.
*/
Expand Down Expand Up @@ -3221,7 +3227,7 @@ class ia {
return t === void 0 ? this._changedLastTick : this._changedLastTick && this.getMarks().some((e) => e.changed(t));
}
}
const ra = 5e3;
const aa = 5e3;
class Z {
constructor(t, e) {
this._timeProvider = null, this._attrNames = [], this._listeners = [], this._graphListeners = [], this._defaultDuration = 1e3, this._defaultCurve = G, this._changedLastTick = !1, this._changedAttributes = {}, this._hitTest = null, this._adjacency = {}, this._reverseAdjacency = /* @__PURE__ */ new Set(), this.represented = void 0, this._updateListeners = {}, this._eventListeners = {}, this.framesWithUpdate = 0, this.id = t, e === void 0 && console.error(
Expand Down Expand Up @@ -3341,7 +3347,7 @@ class Z {
continue;
this.attributes[r].advance(t) ? e = !0 : this._changedAttributes[r] = !1;
}
return e ? (this.framesWithUpdate += 1, this.framesWithUpdate > ra && console.warn("Marks are being updated excessively!"), this._changedLastTick = !0, !0) : (this.framesWithUpdate = 0, this._changedLastTick = !1, !1);
return e ? (this.framesWithUpdate += 1, this.framesWithUpdate > aa && console.warn("Marks are being updated excessively!"), this._changedLastTick = !0, !0) : (this.framesWithUpdate = 0, this._changedLastTick = !1, !1);
}
/**
* Instantaneously sets the value of an attribute, either taking the new
Expand Down Expand Up @@ -3427,7 +3433,7 @@ class Z {
this.attributes[t].last()
);
let a = r.duration === void 0 ? this._defaultDuration : r.duration, s = r.curve === void 0 ? this._defaultCurve : r.curve, n = new z(
V(e),
W(e),
a,
s
).withDelay(r.delay || 0);
Expand All @@ -3453,7 +3459,7 @@ class Z {
if (!_t(a, this.attributes[t].last()) || !_t(a, this.attributes[t].future())) {
let s = e.duration !== void 0 ? e.duration : this._defaultDuration, n = e.curve !== void 0 ? e.curve : this._defaultCurve;
r = new z(
V(a),
W(a),
s,
n
).withDelay(e.delay || 0);
Expand Down Expand Up @@ -3614,9 +3620,9 @@ class fa {
}
function pt(i, t, e) {
e > 0 ? (i[0].animate(
new z(V(t[0]), e, G)
new z(W(t[0]), e, G)
), i[1].animate(
new z(V(t[1]), e, G)
new z(W(t[1]), e, G)
)) : (i[0].set(t[0]), i[1].set(t[1]));
}
class ma {
Expand Down Expand Up @@ -3756,7 +3762,7 @@ class ma {
if (t !== void 0) {
if (this.unfollow(), e) {
let r = (a) => new z(
V(a),
W(a),
this.animationDuration,
G
);
Expand Down Expand Up @@ -3824,11 +3830,11 @@ class ma {
return r.ky || r.k;
}), this._translateX.set(() => this._calculatingTransform ? this._translateX.last() : this._calculateControllerTransform().x), this._translateY.set(() => this._calculatingTransform ? this._translateY.last() : this._calculateControllerTransform().y), e) {
let r = (a) => new z(
V(a),
Qr(() => a.data()),
this.animationDuration,
G
);
this._xScaleFactor.animate(r(this._xScaleFactor.data())), this._yScaleFactor.animate(r(this._yScaleFactor.data())), this._translateX.animate(r(this._translateX.data())), this._translateY.animate(r(this._translateY.data()));
this._xScaleFactor.animate(r(this._xScaleFactor)), this._yScaleFactor.animate(r(this._yScaleFactor)), this._translateX.animate(r(this._translateX)), this._translateY.animate(r(this._translateY));
}
return this;
}
Expand Down Expand Up @@ -3995,7 +4001,7 @@ class _a {
}
_forEachMark(t) {
this.markCollections.forEach((e) => {
if (e instanceof ia)
if (e instanceof ra)
e.forEach(t);
else if (e instanceof Z)
t(e);
Expand Down Expand Up @@ -4135,8 +4141,8 @@ class _a {
return a && a.dispatch(e, r), a;
}
}
var aa = /* @__PURE__ */ ((i) => (i.none = "no-preference", i.more = "more", i.less = "less", i))(aa || {}), sa = /* @__PURE__ */ ((i) => (i.light = "light", i.dark = "dark", i))(sa || {});
class na {
var sa = /* @__PURE__ */ ((i) => (i.none = "no-preference", i.more = "more", i.less = "less", i))(sa || {}), na = /* @__PURE__ */ ((i) => (i.light = "light", i.dark = "dark", i))(na || {});
class oa {
constructor() {
this._hasChanged = !1;
let t = window.matchMedia("(prefers-reduced-motion: reduce)");
Expand Down Expand Up @@ -4201,24 +4207,24 @@ class na {
}
let Ot;
function ya() {
return Ot || (Ot = new na()), Ot;
return Ot || (Ot = new oa()), Ot;
}
export {
z as Animator,
b as Attribute,
bi as AttributeRecompute,
sa as ColorSchemePreference,
aa as ContrastPreference,
na as ColorSchemePreference,
sa as ContrastPreference,
fa as LazyTicker,
Z as Mark,
li as MarkFollower,
ia as MarkRenderGroup,
ra as MarkRenderGroup,
_a as PositionMap,
na as RenderContext,
oa as RenderContext,
ma as Scales,
ea as StageManager,
ta as StagingAction,
Kr as StagingState,
ia as StageManager,
ea as StagingAction,
ta as StagingState,
da as Ticker,
Ht as autoMixingFunction,
ua as basicAnimationTo,
Expand All @@ -4229,8 +4235,8 @@ export {
ca as defineMark,
ya as getRenderContext,
la as interpolateAlongPath,
V as interpolateTo,
ha as interpolateToFunction,
W as interpolateTo,
Qr as interpolateToFunction,
pa as markBox,
Jr as numericalArrayMixingFunction,
hi as numericalMixingFunction
Expand Down
2 changes: 1 addition & 1 deletion counterpoint/dist/counterpoint-vis.umd.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions counterpoint/lib/attribute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ export class Attribute<
*/
set(newValue: ValueType | ((computeArg: ComputeArgumentType) => ValueType)) {
if (typeof newValue == 'function') {
if (this.value != null) this._computedValue = this.value;
if (this.value != null) this._computedValue = this.getUntransformed();
this.valueFn = newValue as (computeArg: ComputeArgumentType) => ValueType;
this.value = undefined;
this._animatedValue = null;
Expand Down Expand Up @@ -560,7 +560,7 @@ export class Attribute<
* function.
*/
last(): ValueType {
if (!!this.animation) {
if (!!this.animation && this._preload) {
// We're in a dirty state - a preloadable animation is underway and
// the attribute hasn't been updated using the advance() method yet.
// Because any updates to the attribute would result in the proper values
Expand Down
23 changes: 16 additions & 7 deletions counterpoint/lib/rendergroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export class MarkRenderGroup<
this.markSet.add(m);
this._setupMark(m);
});
if (!!this.stage) this.stage.setVisibleMarks(this.marks);
this._setupStage();
}

/**
Expand Down Expand Up @@ -192,12 +192,7 @@ export class MarkRenderGroup<
if (!!this.marks) this.getMarks().forEach((m) => this._configureMark(m));

this.useStaging = opts.useStaging ?? this.useStaging;
if (this.useStaging) {
this.stage = new StageManager<AttributeSet>();
if (!!this.marks) this.stage.setVisibleMarks(this.getMarks());
} else {
this.stage = null;
}
this._setupStage();

return this;
}
Expand All @@ -217,6 +212,20 @@ export class MarkRenderGroup<
return this;
}

/**
* Sets up the stage manager if it has not already been set up.
*/
_setupStage() {
if (this.useStaging) {
if (!this.stage) {
this.stage = new StageManager<AttributeSet>();
}
if (!!this.marks) this.stage.setVisibleMarks(this.getMarks());
} else {
this.stage = null;
}
}

/**
* Sets up a mark for the first time.
*/
Expand Down
19 changes: 12 additions & 7 deletions counterpoint/lib/scales.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { Animator, curveEaseInOut, interpolateTo } from './animator';
import {
Animator,
curveEaseInOut,
interpolateTo,
interpolateToFunction,
} from './animator';
import { Attribute } from './attribute';
import { Mark, MarkAttributes } from './mark';
import { TimeProvider, boundingBox, makeTimeProvider } from './utils';
Expand Down Expand Up @@ -473,16 +478,16 @@ export class Scales {
return this._calculateControllerTransform().y;
});
if (animated) {
let animator = (val: number) =>
let animator = (attr: Attribute<number>) =>
new Animator(
interpolateTo(val),
interpolateToFunction(() => attr.data()),
this.animationDuration,
curveEaseInOut
);
this._xScaleFactor.animate(animator(this._xScaleFactor.data()));
this._yScaleFactor.animate(animator(this._yScaleFactor.data()));
this._translateX.animate(animator(this._translateX.data()));
this._translateY.animate(animator(this._translateY.data()));
this._xScaleFactor.animate(animator(this._xScaleFactor));
this._yScaleFactor.animate(animator(this._yScaleFactor));
this._translateX.animate(animator(this._translateX));
this._translateY.animate(animator(this._translateY));
}

return this;
Expand Down
6 changes: 3 additions & 3 deletions examples/cars_bubble/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
<body style="font-family: sans-serif">
<div
id="chart-container"
style="position: relative; flex-shrink: 0; width: 600px; height: 600px"
style="position: relative; flex-shrink: 0; width: 400px; height: 400px"
>
<svg
width="600"
height="600"
width="400"
height="400"
id="axes"
style="position: absolute; top: 0; left: 0"
overflow="visible"
Expand Down
Loading

0 comments on commit 841ed67

Please sign in to comment.