UNPKG

victory-animation

Version:
152 lines (139 loc) 6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } var _lodash = require("lodash"); var _lodash2 = _interopRequireDefault(_lodash); var _d3Interpolate = require("d3-interpolate"); var _d3Interpolate2 = _interopRequireDefault(_d3Interpolate); var isInterpolatable = function isInterpolatable(obj) { // d3 turns null into 0 and undefined into NaN, which we don't want. if (obj !== null) { switch (typeof obj) { case "undefined": return false; case "number": // The standard `isNaN` is fine in this case since we already know the // type is number. return !isNaN(obj) && _lodash2["default"].isFinite(obj); case "string": // d3 might not *actually* be able to interpolate the string, but it // won't cause any issues to let it try. return true; case "boolean": // d3 turns Booleans into integers, which we don't want. Sure, we could // interpolate from 0 -> 1, but we'd be sending a non-Boolean to // something expecting a Boolean. return false; case "object": // Don't try to interpolate class instances (except Date or Array). return _lodash2["default"].isDate(obj) || _lodash2["default"].isArray(obj) || _lodash2["default"].isPlainObject(obj); case "function": // Careful! There may be extra properties on function objects that the // component expects to access - for instance, it may be a `d3.scale()` // function, which has its own methods attached. We don't know if the // component is only going to call the function (in which case it's // safely interpolatable) or if it's going to access special properties // (in which case our function generated from `interpolateFunction` will // most likely cause an error. We could check for enumerable properties // on the function object here to see if it's a "plain" function, but // let's just require that components prevent such function props from // being animated in the first place. return true; } } return false; }; exports.isInterpolatable = isInterpolatable; /** * Interpolate immediately to the end value at the given step `when`. * Some nicer default behavior might be to jump at the halfway point or return * `a` if `t` is 0 (instead of always returning `b`). But d3's default * interpolator does not do these things: * * d3.interpolate('aaa', 'bbb')(0) === 'bbb' * * ...and things might get wonky if we don't replicate that behavior. * * @param {any} a - Start value. * @param {any} b - End value. * @param {Number} when - Step value (0 to 1) at which to jump to `b`. * @returns {Function} An interpolation function. */ var interpolateImmediate = function interpolateImmediate(a, b) { var when = arguments.length <= 2 || arguments[2] === undefined ? 0 : arguments[2]; return function (t) { return t < when ? a : b; }; }; exports.interpolateImmediate = interpolateImmediate; /** * Interpolate to or from a function. The interpolated value will be a function * that calls `a` (if it's a function) and `b` (if it's a function) and calls * `d3.interpolate` on the resulting values. Note that our function won't * necessarily be called (that's up to the component this eventually gets * passed to) - but if it does get called, it will return an appropriately * interpolated value. * * @param {any} a - Start value. * @param {any} b - End value. * @returns {Function} An interpolation function. */ var interpolateFunction = function interpolateFunction(a, b) { return function (t) { if (t >= 1) { return b; } return function () { /* eslint-disable no-invalid-this */ var aval = typeof a === "function" ? a.apply(this, arguments) : a; var bval = typeof b === "function" ? b.apply(this, arguments) : b; return _d3Interpolate2["default"].value(aval, bval)(t); }; }; }; exports.interpolateFunction = interpolateFunction; /** * By default, the list of interpolators used by `d3.interpolate` has a few * downsides: * * - `null` values get turned into 0. * - `undefined`, `function`, and some other value types get turned into NaN. * - Boolean types get turned into numbers, which probably will be meaningless * to whatever is consuming them. * - It tries to interpolate between identical start and end values, doing * unnecessary calculations that sometimes result in floating point rounding * errors. * * If only the default interpolators are used, `VictoryAnimation` will happily * pass down NaN (and other bad) values as props to the wrapped component. * The component will then either use the incorrect values or complain that it * was passed props of the incorrect type. This custom interpolator is added * using the `d3.interpolators` API, and prevents such cases from happening * for most values. * * @param {any} a - Start value. * @param {any} b - End value. * @returns {Function|undefined} An interpolation function, if necessary. */ var victoryInterpolator = function victoryInterpolator(a, b) { // If the values are strictly equal, or either value is not interpolatable, // just use either the start value `a` or end value `b` at every step, as // there is no reasonable in-between value. if (a === b || !isInterpolatable(a) || !isInterpolatable(b)) { return interpolateImmediate(a, b); } if (typeof a === "function" || typeof b === "function") { return interpolateFunction(a, b); } }; exports.victoryInterpolator = victoryInterpolator; var interpolatorAdded = false; var addVictoryInterpolator = function addVictoryInterpolator() { if (!interpolatorAdded) { _d3Interpolate2["default"].values.push(victoryInterpolator); interpolatorAdded = true; } }; exports.addVictoryInterpolator = addVictoryInterpolator;