function inertiaDrag(projection, render, started, moved, ended) { let v0, // Mouse position in Cartesian coordinates at start of drag gesture. r0, // Projection rotation as Euler angles at start. q0; // Projection rotation as versor at start. const limit = 1.0001; const A = 5000; // reference time in ms const B = -Math.log(1 - 1 / limit); const inertia = { start() { const position = d3.mouse(this); inertia.position = position; inertia.velocity = [0, 0]; if (inertia.timer) { inertia.timer.stop(); } // original start-drag v0 = versor.cartesian(projection.invert(position)); r0 = projection.rotate(); q0 = versor(r0); started && started.call(this, position); }, move() { const position = d3.mouse(this); const time = performance.now(); let timeGap = time - inertia.time; const decay = 1 - Math.exp(-timeGap / 1000); inertia.velocity = inertia.velocity.map(function(d, i) { const posGap = position[i] - inertia.position[i]; const decay1 = 1 - decay; const decayD = d * decay; timeGap = time - inertia.time; return 1000 * decay1 * posGap / timeGap + decayD; }); inertia.time = time; inertia.position = position; // original move-drag const inv = projection.rotate(r0).invert(position); if (isNaN(inv[0])) return; const v1 = versor.cartesian(inv), q1 = versor.multiply(q0, versor.delta(v0, v1)), r1 = versor.rotation(q1); render(r1); moved && moved.call(this, position); }, end() { const v = inertia.velocity; if (v[0] * v[0] + v[1] * v[1] < 100) { return; } // versor velocity const p10 = inertia.position.map((d, i) => d - inertia.velocity[i] / 1000), v10 = versor.cartesian(projection.invert(p10)), r10 = projection.rotate(), q10 = versor(r10), p11 = inertia.position, v11 = versor.cartesian(projection.invert(p11)); inertia.timer = d3.timer(function(e) { const t = inertia.t = limit * (1 - Math.exp(-B * e / A)), q11 = versor.multiply(q10, versor.delta(v10, v11, t * 1000)); r11 = versor.rotation(q11); render(r11); if (inertia.t > 1) { inertia.timer.stop(); inertia.velocity = [0, 0]; inertia.t = 1; } }); ended && ended.call(this, inertia.position); }, position: [0, 0], velocity: [0, 0], // in pixels/s timer: null, time: 0, } return inertia; };