|
(function (global, factory) { |
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-interpolate')) : |
|
typeof define === 'function' && define.amd ? define(['exports', 'd3-interpolate'], factory) : |
|
(factory((global.d3 = global.d3 || {}),global.d3)); |
|
}(this, (function (exports,d3Interpolate) { 'use strict'; |
|
|
|
var _extends = Object.assign || function (target) { |
|
for (var i = 1; i < arguments.length; i++) { |
|
var source = arguments[i]; |
|
|
|
for (var key in source) { |
|
if (Object.prototype.hasOwnProperty.call(source, key)) { |
|
target[key] = source[key]; |
|
} |
|
} |
|
} |
|
|
|
return target; |
|
}; |
|
|
|
/** |
|
* List of params for each command type in a path `d` attribute |
|
*/ |
|
var typeMap = { |
|
M: ['x', 'y'], |
|
L: ['x', 'y'], |
|
H: ['x'], |
|
V: ['y'], |
|
C: ['x1', 'y1', 'x2', 'y2', 'x', 'y'], |
|
S: ['x2', 'y2', 'x', 'y'], |
|
Q: ['x1', 'y1', 'x', 'y'], |
|
T: ['x', 'y'], |
|
A: ['rx', 'ry', 'xAxisRotation', 'largeArcFlag', 'sweepFlag', 'x', 'y'] |
|
}; |
|
|
|
/** |
|
* Convert to object representation of the command from a string |
|
* |
|
* @param {String} commandString Token string from the `d` attribute (e.g., L0,0) |
|
* @return {Object} An object representing this command. |
|
*/ |
|
function commandObject(commandString) { |
|
// convert all spaces to commas |
|
commandString = commandString.trim().replace(/ /g, ','); |
|
|
|
var type = commandString[0]; |
|
var args = commandString.substring(1).split(','); |
|
return typeMap[type.toUpperCase()].reduce(function (obj, param, i) { |
|
// parse X as float since we need it to do distance checks for extending points |
|
obj[param] = param === 'x' ? parseFloat(args[i]) : args[i]; |
|
return obj; |
|
}, { type: type }); |
|
} |
|
|
|
/** |
|
* Converts a command object to a string to be used in a `d` attribute |
|
* @param {Object} command A command object |
|
* @return {String} The string for the `d` attribute |
|
*/ |
|
function commandToString(command) { |
|
var type = command.type; |
|
|
|
var params = typeMap[type.toUpperCase()]; |
|
return '' + type + params.map(function (p) { |
|
return command[p]; |
|
}).join(','); |
|
} |
|
|
|
/** |
|
* Converts command A to have the same type as command B. |
|
* |
|
* e.g., L0,5 -> C0,5,0,5,0,5 |
|
* |
|
* Uses these rules: |
|
* x1 <- x |
|
* x2 <- x |
|
* y1 <- y |
|
* y2 <- y |
|
* rx <- 0 |
|
* ry <- 0 |
|
* xAxisRotation <- read from B |
|
* largeArcFlag <- read from B |
|
* sweepflag <- read from B |
|
* |
|
* @param {Object} aCommand Command object from path `d` attribute |
|
* @param {Object} bCommand Command object from path `d` attribute to match against |
|
* @return {Object} aCommand converted to type of bCommand |
|
*/ |
|
function convertToSameType(aCommand, bCommand) { |
|
var conversionMap = { |
|
x1: 'x', |
|
y1: 'y', |
|
x2: 'x', |
|
y2: 'y' |
|
}; |
|
|
|
var readFromBKeys = ['xAxisRotation', 'largeArcFlag', 'sweepFlag']; |
|
|
|
// convert (but ignore M types) |
|
if (aCommand.type !== bCommand.type && bCommand.type.toUpperCase() !== 'M') { |
|
(function () { |
|
var aConverted = {}; |
|
Object.keys(bCommand).forEach(function (bKey) { |
|
var bValue = bCommand[bKey]; |
|
// first read from the A command |
|
var aValue = aCommand[bKey]; |
|
|
|
// if it is one of these values, read from B no matter what |
|
if (aValue === undefined) { |
|
if (readFromBKeys.includes(bKey)) { |
|
aValue = bValue; |
|
} else { |
|
// if it wasn't in the A command, see if an equivalent was |
|
if (aValue === undefined && conversionMap[bKey]) { |
|
aValue = aCommand[conversionMap[bKey]]; |
|
} |
|
|
|
// if it doesn't have a converted value, use 0 |
|
if (aValue === undefined) { |
|
aValue = 0; |
|
} |
|
} |
|
} |
|
|
|
aConverted[bKey] = aValue; |
|
}); |
|
|
|
// update the type to match B |
|
aConverted.type = bCommand.type; |
|
aCommand = aConverted; |
|
})(); |
|
} |
|
|
|
return aCommand; |
|
} |
|
|
|
/** |
|
* Extends an array of commands to the length of the second array |
|
* inserting points at the spot that is closest by X value. Ensures |
|
* all the points of commandsToExtend are in the extended array and that |
|
* only numPointsToExtend points are added. |
|
* |
|
* @param {Object[]} commandsToExtend The commands array to extend |
|
* @param {Object[]} referenceCommands The commands array to match |
|
* @return {Object[]} The extended commands1 array |
|
*/ |
|
function extend(commandsToExtend, referenceCommands, numPointsToExtend) { |
|
// map each command in B to a command in A by counting how many times ideally |
|
// a command in A was in the initial path (see https://github.com/pbeshai/d3-interpolate-path/issues/8) |
|
var initialCommandIndex = void 0; |
|
if (commandsToExtend.length > 1 && commandsToExtend[0].type === 'M') { |
|
initialCommandIndex = 1; |
|
} else { |
|
initialCommandIndex = 0; |
|
} |
|
|
|
var counts = referenceCommands.reduce(function (counts, refCommand, i) { |
|
// skip first M |
|
if (i === 0 && refCommand.type === 'M') { |
|
counts[0] = 1; |
|
return counts; |
|
} |
|
|
|
var minDistance = Math.abs(commandsToExtend[initialCommandIndex].x - refCommand.x); |
|
var minCommand = initialCommandIndex; |
|
|
|
// find the closest point by X position in A |
|
for (var j = initialCommandIndex + 1; j < commandsToExtend.length; j++) { |
|
var distance = Math.abs(commandsToExtend[j].x - refCommand.x); |
|
if (distance < minDistance) { |
|
minDistance = distance; |
|
minCommand = j; |
|
// since we assume sorted by X, once we find a value farther, we can return the min. |
|
} else { |
|
break; |
|
} |
|
} |
|
|
|
counts[minCommand] = (counts[minCommand] || 0) + 1; |
|
return counts; |
|
}, {}); |
|
|
|
// now extend the array adding in at the appropriate place as needed |
|
var extended = []; |
|
var numExtended = 0; |
|
for (var i = 0; i < commandsToExtend.length; i++) { |
|
// add in the initial point for this A command |
|
extended.push(commandsToExtend[i]); |
|
|
|
for (var j = 1; j < counts[i] && numExtended < numPointsToExtend; j++) { |
|
var commandToAdd = _extends({}, commandsToExtend[i]); |
|
// don't allow multiple Ms |
|
if (commandToAdd.type === 'M') { |
|
commandToAdd.type = 'L'; |
|
} else { |
|
// try to set control points to x and y |
|
if (commandToAdd.x1 !== undefined) { |
|
commandToAdd.x1 = commandToAdd.x; |
|
commandToAdd.y1 = commandToAdd.y; |
|
} |
|
|
|
if (commandToAdd.x2 !== undefined) { |
|
commandToAdd.x2 = commandToAdd.x; |
|
commandToAdd.y2 = commandToAdd.y; |
|
} |
|
} |
|
extended.push(commandToAdd); |
|
numExtended += 1; |
|
} |
|
} |
|
|
|
return extended; |
|
} |
|
|
|
/** |
|
* Interpolate from A to B by extending A and B during interpolation to have |
|
* the same number of points. This allows for a smooth transition when they |
|
* have a different number of points. |
|
* |
|
* Ignores the `Z` character in paths unless both A and B end with it. |
|
* |
|
* @param {String} a The `d` attribute for a path |
|
* @param {String} b The `d` attribute for a path |
|
*/ |
|
function interpolatePath(a, b) { |
|
// remove Z, remove spaces after letters as seen in IE |
|
var aNormalized = a == null ? '' : a.replace(/[Z]/gi, '').replace(/([MLCSTQAHV])\s*/gi, '$1'); |
|
var bNormalized = b == null ? '' : b.replace(/[Z]/gi, '').replace(/([MLCSTQAHV])\s*/gi, '$1'); |
|
var aPoints = aNormalized === '' ? [] : aNormalized.split(/(?=[MLCSTQAHV])/gi); |
|
var bPoints = bNormalized === '' ? [] : bNormalized.split(/(?=[MLCSTQAHV])/gi); |
|
|
|
// if both are empty, interpolation is always the empty string. |
|
if (!aPoints.length && !bPoints.length) { |
|
return function nullInterpolator() { |
|
return ''; |
|
}; |
|
} |
|
|
|
// if A is empty, treat it as if it used to contain just the first point |
|
// of B. This makes it so the line extends out of from that first point. |
|
if (!aPoints.length) { |
|
aPoints.push(bPoints[0]); |
|
|
|
// otherwise if B is empty, treat it as if it contains the first point |
|
// of A. This makes it so the line retracts into the first point. |
|
} else if (!bPoints.length) { |
|
bPoints.push(aPoints[0]); |
|
} |
|
|
|
// convert to command objects so we can match types |
|
var aCommands = aPoints.map(commandObject); |
|
var bCommands = bPoints.map(commandObject); |
|
|
|
// extend to match equal size |
|
var numPointsToExtend = Math.abs(bPoints.length - aPoints.length); |
|
|
|
if (numPointsToExtend !== 0) { |
|
// B has more points than A, so add points to A before interpolating |
|
if (bCommands.length > aCommands.length) { |
|
aCommands = extend(aCommands, bCommands, numPointsToExtend); |
|
|
|
// else if A has more points than B, add more points to B |
|
} else if (bCommands.length < aCommands.length) { |
|
bCommands = extend(bCommands, aCommands, numPointsToExtend); |
|
} |
|
} |
|
|
|
// commands have same length now. |
|
// convert A to the same type of B |
|
aCommands = aCommands.map(function (aCommand, i) { |
|
return convertToSameType(aCommand, bCommands[i]); |
|
}); |
|
|
|
var aProcessed = aCommands.map(commandToString).join(''); |
|
var bProcessed = bCommands.map(commandToString).join(''); |
|
|
|
// if both A and B end with Z add it back in |
|
if ((a == null || a[a.length - 1] === 'Z') && (b == null || b[b.length - 1] === 'Z')) { |
|
aProcessed += 'Z'; |
|
bProcessed += 'Z'; |
|
} |
|
|
|
var stringInterpolator = d3Interpolate.interpolateString(aProcessed, bProcessed); |
|
|
|
return function pathInterpolator(t) { |
|
// at 1 return the final value without the extensions used during interpolation |
|
if (t === 1) { |
|
return b == null ? '' : b; |
|
} |
|
|
|
return stringInterpolator(t); |
|
}; |
|
} |
|
|
|
exports.interpolatePath = interpolatePath; |
|
|
|
Object.defineProperty(exports, '__esModule', { value: true }); |
|
|
|
}))); |