victory-animation
Version:
animation wrapper for victory components
152 lines (139 loc) • 6 kB
JavaScript
;
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;