Showing preview only (351K chars total). Download the full file or copy to clipboard to get everything.
Repository: pbeshai/d3-interpolate-path
Branch: master
Commit: 293005ecceff
Files: 22
Total size: 339.5 KB
Directory structure:
gitextract__y584mo5/
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── docs/
│ ├── d3-interpolate-path.js
│ ├── example.css
│ ├── examples.js
│ ├── index.html
│ └── v1/
│ ├── d3-interpolate-path-v1.1.1.js
│ ├── example.css
│ ├── examples.js
│ └── index.html
├── index.js
├── package.json
├── rollup.config.js
├── src/
│ ├── .babelrc
│ ├── interpolatePath.js
│ └── split.js
└── test/
├── interpolatePath-test.js
└── interpolatePathCommands-test.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintignore
================================================
rollup.config.js
================================================
FILE: .eslintrc.js
================================================
module.exports = {
env: {
browser: true,
es6: true,
node: true,
},
extends: ['eslint:recommended', 'prettier'],
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
ecmaFeatures: {
jsx: false,
},
},
globals: {},
rules: {
'no-unused-vars': 'warn',
'no-empty': 'warn',
},
};
================================================
FILE: .gitignore
================================================
.DS_Store
build/
example/d3-scale-interactive.js
example/d3-scale-interactive.css
node_modules
npm-debug.log
================================================
FILE: .npmignore
================================================
build/*.zip
test/
================================================
FILE: LICENSE
================================================
Copyright 2016, Peter Beshai
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the author nor the names of contributors may be used to
endorse or promote products derived from this software without specific prior
written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
# d3-interpolate-path
[](https://badge.fury.io/js/d3-interpolate-path)
d3-interpolate-path is a zero-dependency that adds an [interpolator](https://github.com/d3/d3-interpolate)
optimized for SVG <path> elements. It can also work directly with object representations of path commands that can be later interpreted for use with canvas or WebGL.
**Note this package no longer has a dependency on d3 or d3-interpolate**, but in web usage adds itself to the d3 namespace like other d3 plugins do.
Blog: [Improving D3 Path Animation](https://bocoup.com/weblog/improving-d3-path-animation)
Demo: https://pbeshai.github.io/d3-interpolate-path/

## Example Usage
```js
var line = d3.line()
.curve(d3.curveLinear)
.x(function (d) { return x(d.x); })
.y(function (d) { return y(d.y); });
d3.select('path.my-path')
.transition()
.duration(2000)
.attrTween('d', function (d) {
var previous = d3.select(this).attr('d');
var current = line(d);
return d3.interpolatePath(previous, current);
});
```
If you're using it in a module environment, you can import it as follows:
```js
import { interpolatePath } from 'd3-interpolate-path';
```
Otherwise, you can use it via a `<script>` tag as follows:
```js
<script src="https://unpkg.com/d3-interpolate-path/build/d3-interpolate-path.min.js"></script>
```
## Development
Get rollup watching for changes and rebuilding
```bash
npm run watch
```
Run a web server in the docs directory
```bash
cd docs
php -S localhost:8000
```
Go to http://localhost:8000
## Installing
If you use NPM, `npm install d3-interpolate-path`. Otherwise, download the [latest release](https://github.com/pbeshai/d3-interpolate-path/releases/latest).
## API Reference
<a href="#interpolatePath" name="interpolatePath">#</a> <b>interpolatePath</b>(*a*, *b*, *excludeSegment*)
Returns an interpolator between two path attribute `d` strings *a* and *b*. The interpolator extends *a* and *b* to have the same number of points before applying linear interpolation on the values. It uses De Castlejau's algorithm for handling bezier curves.
```js
var pathInterpolator = interpolatePath('M0,0 L10,10', 'M10,10 L20,20 L30,30')
pathInterpolator(0) // 'M0,0 L10,10 L10,10'
pathInterpolator(0.5) // 'M5,5 L15,15 L20,20'
pathInterpolator(1) // 'M10,10 L20,20 L30,30'
```
You can optionally provide a function *excludeSegment* that takes two adjacent path commands and returns true if that segment should be excluded when splitting the line. A command object has form `{ type, x, y }` (with possibly more attributes depending on type). An example object:
```js
// equivalent to M0,150 in a path `d` string
{
type: 'M',
x: 0,
y: 150
}
```
This is most useful when working with d3-area. Excluding the final segment (i.e. the vertical line at the end) from being split ensures a nice transition. If you know that highest `x` value in the path, you can exclude the final segment by passing an excludeSegment function similar to:
```js
function excludeSegment(a, b) {
return a.x === b.x && a.x === 300; // here 300 is the max X
}
```
<a href="#interpolatePathCommands" name="interpolatePathCommands">#</a> <b>interpolatePathCommands</b>(*aCommands*, *bCommands*, *excludeSegment*)
Returns an interpolator between two paths defined as arrays of command objects *a* and *b*. The interpolator extends *a* and *b* to have the same number of points if they differ. This can be useful if you want to work with paths in other formats besides SVG (e.g. canvas or WebGL).
Command objects take the following form:
```ts
| { type: 'M', x: number, y: number },
| { type: 'L', x, y }
| { type: 'H', x }
| { type: 'V', y }
| { type: 'C', x1, y1, x2, y2, x, y }
| { type: 'S', x2, y2, x, y }
| { type: 'Q', x1, y1, x, y }
| { type: 'T', x, y }
| { type: 'A', rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y }
| { type: 'Z' }
```
Example usage:
```js
const a = [
{ type: 'M', x: 0, y: 0 },
{ type: 'L', x: 10, y: 10 },
];
const b = [
{ type: 'M', x: 10, y: 10 },
{ type: 'L', x: 20, y: 20 },
{ type: 'L', x: 200, y: 200 },
];
const interpolator = interpolatePathCommands(a, b);
> interpolator(0);
[
{ type: 'M', x: 0, y: 0 },
{ type: 'L', x: 5, y: 5 },
{ type: 'L', x: 10, y: 10 },
]
> interpolator(0.5);
[
{ type: 'M', x: 5, y: 5 },
{ type: 'L', x: 12.5, y: 12.5 },
{ type: 'L', x: 105, y: 105 },
]
```
<a href="#pathCommandsFromString" name="pathCommandsFromString">#</a> <b>pathCommandsFromString</b>(*pathDString*)
Converts a path `d` string into an array of path command objects to work with [**interpolatePathCommands**](#interpolatePathCommands).
Example usage:
```js
const a = 'M0,0L10,10';
> pathCommandsFromString(a)
[
{ type: 'M', x: 0, y: 0 },
{ type: 'L', x: 10, y: 10 },
]
```
================================================
FILE: docs/d3-interpolate-path.js
================================================
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.d3 = global.d3 || {}));
}(this, (function (exports) { 'use strict';
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly) {
symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
});
}
keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread2(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
if (i % 2) {
ownKeys(Object(source), true).forEach(function (key) {
_defineProperty(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
}
return target;
}
function _typeof(obj) {
"@babel/helpers - typeof";
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function (obj) {
return typeof obj;
};
} else {
_typeof = function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
}
return _typeof(obj);
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _extends() {
_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;
};
return _extends.apply(this, arguments);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _createForOfIteratorHelper(o, allowArrayLike) {
var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
if (!it) {
if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
if (it) o = it;
var i = 0;
var F = function () {};
return {
s: F,
n: function () {
if (i >= o.length) return {
done: true
};
return {
done: false,
value: o[i++]
};
},
e: function (e) {
throw e;
},
f: F
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
var normalCompletion = true,
didErr = false,
err;
return {
s: function () {
it = it.call(o);
},
n: function () {
var step = it.next();
normalCompletion = step.done;
return step;
},
e: function (e) {
didErr = true;
err = e;
},
f: function () {
try {
if (!normalCompletion && it.return != null) it.return();
} finally {
if (didErr) throw err;
}
}
};
}
/**
* de Casteljau's algorithm for drawing and splitting bezier curves.
* Inspired by https://pomax.github.io/bezierinfo/
*
* @param {Number[][]} points Array of [x,y] points: [start, control1, control2, ..., end]
* The original segment to split.
* @param {Number} t Where to split the curve (value between [0, 1])
* @return {Object} An object { left, right } where left is the segment from 0..t and
* right is the segment from t..1.
*/
function decasteljau(points, t) {
var left = [];
var right = [];
function decasteljauRecurse(points, t) {
if (points.length === 1) {
left.push(points[0]);
right.push(points[0]);
} else {
var newPoints = Array(points.length - 1);
for (var i = 0; i < newPoints.length; i++) {
if (i === 0) {
left.push(points[0]);
}
if (i === newPoints.length - 1) {
right.push(points[i + 1]);
}
newPoints[i] = [(1 - t) * points[i][0] + t * points[i + 1][0], (1 - t) * points[i][1] + t * points[i + 1][1]];
}
decasteljauRecurse(newPoints, t);
}
}
if (points.length) {
decasteljauRecurse(points, t);
}
return {
left: left,
right: right.reverse()
};
}
/**
* Convert segments represented as points back into a command object
*
* @param {Number[][]} points Array of [x,y] points: [start, control1, control2, ..., end]
* Represents a segment
* @return {Object} A command object representing the segment.
*/
function pointsToCommand(points) {
var command = {};
if (points.length === 4) {
command.x2 = points[2][0];
command.y2 = points[2][1];
}
if (points.length >= 3) {
command.x1 = points[1][0];
command.y1 = points[1][1];
}
command.x = points[points.length - 1][0];
command.y = points[points.length - 1][1];
if (points.length === 4) {
// start, control1, control2, end
command.type = 'C';
} else if (points.length === 3) {
// start, control, end
command.type = 'Q';
} else {
// start, end
command.type = 'L';
}
return command;
}
/**
* Runs de Casteljau's algorithm enough times to produce the desired number of segments.
*
* @param {Number[][]} points Array of [x,y] points for de Casteljau (the initial segment to split)
* @param {Number} segmentCount Number of segments to split the original into
* @return {Number[][][]} Array of segments
*/
function splitCurveAsPoints(points, segmentCount) {
segmentCount = segmentCount || 2;
var segments = [];
var remainingCurve = points;
var tIncrement = 1 / segmentCount; // x-----x-----x-----x
// t= 0.33 0.66 1
// x-----o-----------x
// r= 0.33
// x-----o-----x
// r= 0.5 (0.33 / (1 - 0.33)) === tIncrement / (1 - (tIncrement * (i - 1))
// x-----x-----x-----x----x
// t= 0.25 0.5 0.75 1
// x-----o----------------x
// r= 0.25
// x-----o----------x
// r= 0.33 (0.25 / (1 - 0.25))
// x-----o----x
// r= 0.5 (0.25 / (1 - 0.5))
for (var i = 0; i < segmentCount - 1; i++) {
var tRelative = tIncrement / (1 - tIncrement * i);
var split = decasteljau(remainingCurve, tRelative);
segments.push(split.left);
remainingCurve = split.right;
} // last segment is just to the end from the last point
segments.push(remainingCurve);
return segments;
}
/**
* Convert command objects to arrays of points, run de Casteljau's algorithm on it
* to split into to the desired number of segments.
*
* @param {Object} commandStart The start command object
* @param {Object} commandEnd The end command object
* @param {Number} segmentCount The number of segments to create
* @return {Object[]} An array of commands representing the segments in sequence
*/
function splitCurve(commandStart, commandEnd, segmentCount) {
var points = [[commandStart.x, commandStart.y]];
if (commandEnd.x1 != null) {
points.push([commandEnd.x1, commandEnd.y1]);
}
if (commandEnd.x2 != null) {
points.push([commandEnd.x2, commandEnd.y2]);
}
points.push([commandEnd.x, commandEnd.y]);
return splitCurveAsPoints(points, segmentCount).map(pointsToCommand);
}
var commandTokenRegex = /[MLCSTQAHVZmlcstqahv]|-?[\d.e+-]+/g;
/**
* 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'],
Z: []
}; // Add lower case entries too matching uppercase (e.g. 'm' == 'M')
Object.keys(typeMap).forEach(function (key) {
typeMap[key.toLowerCase()] = typeMap[key];
});
function arrayOfLength(length, value) {
var array = Array(length);
for (var i = 0; i < length; i++) {
array[i] = value;
}
return array;
}
/**
* 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) {
return "".concat(command.type).concat(typeMap[command.type].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') {
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;
}
/**
* Interpolate between command objects commandStart and commandEnd segmentCount times.
* If the types are L, Q, or C then the curves are split as per de Casteljau's algorithm.
* Otherwise we just copy commandStart segmentCount - 1 times, finally ending with commandEnd.
*
* @param {Object} commandStart Command object at the beginning of the segment
* @param {Object} commandEnd Command object at the end of the segment
* @param {Number} segmentCount The number of segments to split this into. If only 1
* Then [commandEnd] is returned.
* @return {Object[]} Array of ~segmentCount command objects between commandStart and
* commandEnd. (Can be segmentCount+1 objects if commandStart is type M).
*/
function splitSegment(commandStart, commandEnd, segmentCount) {
var segments = []; // line, quadratic bezier, or cubic bezier
if (commandEnd.type === 'L' || commandEnd.type === 'Q' || commandEnd.type === 'C') {
segments = segments.concat(splitCurve(commandStart, commandEnd, segmentCount)); // general case - just copy the same point
} else {
var copyCommand = _extends({}, commandStart); // convert M to L
if (copyCommand.type === 'M') {
copyCommand.type = 'L';
}
segments = segments.concat(arrayOfLength(segmentCount - 1).map(function () {
return copyCommand;
}));
segments.push(commandEnd);
}
return segments;
}
/**
* Extends an array of commandsToExtend to the length of the referenceCommands by
* splitting segments until the number of commands match. Ensures all the actual
* points of commandsToExtend are in the extended array.
*
* @param {Object[]} commandsToExtend The command object array to extend
* @param {Object[]} referenceCommands The command object array to match in length
* @param {Function} excludeSegment a function that takes a start command object and
* end command object and returns true if the segment should be excluded from splitting.
* @return {Object[]} The extended commandsToExtend array
*/
function extend(commandsToExtend, referenceCommands, excludeSegment) {
// compute insertion points:
// number of segments in the path to extend
var numSegmentsToExtend = commandsToExtend.length - 1; // number of segments in the reference path.
var numReferenceSegments = referenceCommands.length - 1; // this value is always between [0, 1].
var segmentRatio = numSegmentsToExtend / numReferenceSegments; // create a map, mapping segments in referenceCommands to how many points
// should be added in that segment (should always be >= 1 since we need each
// point itself).
// 0 = segment 0-1, 1 = segment 1-2, n-1 = last vertex
var countPointsPerSegment = arrayOfLength(numReferenceSegments).reduce(function (accum, d, i) {
var insertIndex = Math.floor(segmentRatio * i); // handle excluding segments
if (excludeSegment && insertIndex < commandsToExtend.length - 1 && excludeSegment(commandsToExtend[insertIndex], commandsToExtend[insertIndex + 1])) {
// set the insertIndex to the segment that this point should be added to:
// round the insertIndex essentially so we split half and half on
// neighbouring segments. hence the segmentRatio * i < 0.5
var addToPriorSegment = segmentRatio * i % 1 < 0.5; // only skip segment if we already have 1 point in it (can't entirely remove a segment)
if (accum[insertIndex]) {
// TODO - Note this is a naive algorithm that should work for most d3-area use cases
// but if two adjacent segments are supposed to be skipped, this will not perform as
// expected. Could be updated to search for nearest segment to place the point in, but
// will only do that if necessary.
// add to the prior segment
if (addToPriorSegment) {
if (insertIndex > 0) {
insertIndex -= 1; // not possible to add to previous so adding to next
} else if (insertIndex < commandsToExtend.length - 1) {
insertIndex += 1;
} // add to next segment
} else if (insertIndex < commandsToExtend.length - 1) {
insertIndex += 1; // not possible to add to next so adding to previous
} else if (insertIndex > 0) {
insertIndex -= 1;
}
}
}
accum[insertIndex] = (accum[insertIndex] || 0) + 1;
return accum;
}, []); // extend each segment to have the correct number of points for a smooth interpolation
var extended = countPointsPerSegment.reduce(function (extended, segmentCount, i) {
// if last command, just add `segmentCount` number of times
if (i === commandsToExtend.length - 1) {
var lastCommandCopies = arrayOfLength(segmentCount, _extends({}, commandsToExtend[commandsToExtend.length - 1])); // convert M to L
if (lastCommandCopies[0].type === 'M') {
lastCommandCopies.forEach(function (d) {
d.type = 'L';
});
}
return extended.concat(lastCommandCopies);
} // otherwise, split the segment segmentCount times.
return extended.concat(splitSegment(commandsToExtend[i], commandsToExtend[i + 1], segmentCount));
}, []); // add in the very first point since splitSegment only adds in the ones after it
extended.unshift(commandsToExtend[0]);
return extended;
}
/**
* Takes a path `d` string and converts it into an array of command
* objects. Drops the `Z` character.
*
* @param {String|null} d A path `d` string
*/
function pathCommandsFromString(d) {
// split into valid tokens
var tokens = (d || '').match(commandTokenRegex) || [];
var commands = [];
var commandArgs;
var command; // iterate over each token, checking if we are at a new command
// by presence in the typeMap
for (var i = 0; i < tokens.length; ++i) {
commandArgs = typeMap[tokens[i]]; // new command found:
if (commandArgs) {
command = {
type: tokens[i]
}; // add each of the expected args for this command:
for (var a = 0; a < commandArgs.length; ++a) {
command[commandArgs[a]] = +tokens[i + a + 1];
} // need to increment our token index appropriately since
// we consumed token args
i += commandArgs.length;
commands.push(command);
}
}
return commands;
}
/**
* 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` command in paths unless both A and B end with it.
*
* This function works directly with arrays of command objects instead of with
* path `d` strings (see interpolatePath for working with `d` strings).
*
* @param {Object[]} aCommandsInput Array of path commands
* @param {Object[]} bCommandsInput Array of path commands
* @param {(Function|Object)} interpolateOptions
* @param {Function} interpolateOptions.excludeSegment a function that takes a start command object and
* end command object and returns true if the segment should be excluded from splitting.
* @param {Boolean} interpolateOptions.snapEndsToInput a boolean indicating whether end of input should
* be sourced from input argument or computed.
* @returns {Function} Interpolation function that maps t ([0, 1]) to an array of path commands.
*/
function interpolatePathCommands(aCommandsInput, bCommandsInput, interpolateOptions) {
// make a copy so we don't mess with the input arrays
var aCommands = aCommandsInput == null ? [] : aCommandsInput.slice();
var bCommands = bCommandsInput == null ? [] : bCommandsInput.slice();
var _ref = _typeof(interpolateOptions) === 'object' ? interpolateOptions : {
excludeSegment: interpolateOptions,
snapEndsToInput: true
},
excludeSegment = _ref.excludeSegment,
snapEndsToInput = _ref.snapEndsToInput; // both input sets are empty, so we don't interpolate
if (!aCommands.length && !bCommands.length) {
return function nullInterpolator() {
return [];
};
} // do we add Z during interpolation? yes if both have it. (we'd expect both to have it or not)
var addZ = (aCommands.length === 0 || aCommands[aCommands.length - 1].type === 'Z') && (bCommands.length === 0 || bCommands[bCommands.length - 1].type === 'Z'); // we temporarily remove Z
if (aCommands.length > 0 && aCommands[aCommands.length - 1].type === 'Z') {
aCommands.pop();
}
if (bCommands.length > 0 && bCommands[bCommands.length - 1].type === 'Z') {
bCommands.pop();
} // 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 (!aCommands.length) {
aCommands.push(bCommands[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 (!bCommands.length) {
bCommands.push(aCommands[0]);
} // extend to match equal size
var numPointsToExtend = Math.abs(bCommands.length - aCommands.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, excludeSegment); // else if A has more points than B, add more points to B
} else if (bCommands.length < aCommands.length) {
bCommands = extend(bCommands, aCommands, excludeSegment);
}
} // commands have same length now.
// convert commands in A to the same type as those in B
aCommands = aCommands.map(function (aCommand, i) {
return convertToSameType(aCommand, bCommands[i]);
}); // create mutable interpolated command objects
var interpolatedCommands = aCommands.map(function (aCommand) {
return _objectSpread2({}, aCommand);
});
if (addZ) {
interpolatedCommands.push({
type: 'Z'
});
aCommands.push({
type: 'Z'
}); // required for when returning at t == 0
}
return function pathCommandInterpolator(t) {
// at 1 return the final value without the extensions used during interpolation
if (t === 1 && snapEndsToInput) {
return bCommandsInput == null ? [] : bCommandsInput;
} // work with aCommands directly since interpolatedCommands are mutated
if (t === 0) {
return aCommands;
} // interpolate the commands using the mutable interpolated command objs
for (var i = 0; i < interpolatedCommands.length; ++i) {
// if (interpolatedCommands[i].type === 'Z') continue;
var aCommand = aCommands[i];
var bCommand = bCommands[i];
var interpolatedCommand = interpolatedCommands[i];
var _iterator = _createForOfIteratorHelper(typeMap[interpolatedCommand.type]),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var arg = _step.value;
interpolatedCommand[arg] = (1 - t) * aCommand[arg] + t * bCommand[arg]; // do not use floats for flags (#27), round to integer
if (arg === 'largeArcFlag' || arg === 'sweepFlag') {
interpolatedCommand[arg] = Math.round(interpolatedCommand[arg]);
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
}
return interpolatedCommands;
};
}
/** @typedef InterpolateOptions */
/**
* 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
* @param {((command1, command2) => boolean|{
* excludeSegment?: (command1, command2) => boolean;
* snapEndsToInput?: boolean
* })} interpolateOptions The excludeSegment function or an options object
* - interpolateOptions.excludeSegment a function that takes a start command object and
* end command object and returns true if the segment should be excluded from splitting.
* - interpolateOptions.snapEndsToInput a boolean indicating whether end of input should
* be sourced from input argument or computed.
* @returns {Function} Interpolation function that maps t ([0, 1]) to a path `d` string.
*/
function interpolatePath(a, b, interpolateOptions) {
var aCommands = pathCommandsFromString(a);
var bCommands = pathCommandsFromString(b);
var _ref2 = _typeof(interpolateOptions) === 'object' ? interpolateOptions : {
excludeSegment: interpolateOptions,
snapEndsToInput: true
},
excludeSegment = _ref2.excludeSegment,
snapEndsToInput = _ref2.snapEndsToInput;
if (!aCommands.length && !bCommands.length) {
return function nullInterpolator() {
return '';
};
}
var commandInterpolator = interpolatePathCommands(aCommands, bCommands, {
excludeSegment: excludeSegment,
snapEndsToInput: snapEndsToInput
});
return function pathStringInterpolator(t) {
// at 1 return the final value without the extensions used during interpolation
if (t === 1 && snapEndsToInput) {
return b == null ? '' : b;
}
var interpolatedCommands = commandInterpolator(t); // convert to a string (fastest concat: https://jsperf.com/join-concat/150)
var interpolatedString = '';
var _iterator2 = _createForOfIteratorHelper(interpolatedCommands),
_step2;
try {
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
var interpolatedCommand = _step2.value;
interpolatedString += commandToString(interpolatedCommand);
}
} catch (err) {
_iterator2.e(err);
} finally {
_iterator2.f();
}
return interpolatedString;
};
}
exports.interpolatePath = interpolatePath;
exports.interpolatePathCommands = interpolatePathCommands;
exports.pathCommandsFromString = pathCommandsFromString;
Object.defineProperty(exports, '__esModule', { value: true });
})));
================================================
FILE: docs/example.css
================================================
.main-header {
margin: 0 0 4px;
}
.main-link a {
color: #888;
}
.main-link {
margin-top: 0;
}
body {
font: 14px sans-serif;
padding: 15px;
}
.description {
max-width: 800px;
}
path {
stroke: #0bb;
stroke-width: 1.5px;
fill: none;
}
path.filled {
fill: #0bb;
fill-opacity: 0.2;
}
.example {
display: inline-block;
margin-right: 10px;
margin-bottom: 10px;
border: 1px solid #ccc;
vertical-align: top;
}
.example h4 {
margin: 0;
}
.path-d-string {
width: 80px;
display: inline-block;
overflow: hidden;
margin-right: 15px;
margin-top: 8px;
font-size: 12px;
font-family: sans-serif;
vertical-align: top;
}
.status, .status button {
font-size: 20px;
margin-bottom: 10px;
}
.example-container {
display: inline-block;
padding: 8px;
}
.using-d3-default {
background: #eee;
color: #666;
}
.using-d3-default h4 {
font-weight: 400;
}
.using-d3-default path {
stroke: #b1a776;
}
.using-d3-default path.filled {
fill: #bcb38b;
}
.interpolator-used {
color: #666;
font-size: 0.8em;
}
================================================
FILE: docs/examples.js
================================================
/**
* Apologies for this code. It's kind of hacked together to quickly demonstrate things.
*/
var exampleWidth = 250;
var exampleHeight = 200;
var showMainExample = !window.location.search.includes('showMainExample=0');
var showPathValues = window.location.search.includes('showPathValues=1');
var optionShowPathPoints = window.location.search.includes('showPathPoints=1');
var maxNumLoops = 10; // comment out for infinite looping
// var activeExamples = [13]; // comment out for all examples
// var activeExamples = [2]; // comment out for all examples
var delayTime = 0; // 1000
var duration = 3000; // 2000
console.log('Show Main Example', showMainExample);
console.log('Show Path Values', showPathValues);
// helper to loop a path between two points
function loopPathBasic(path, dPath1, dPath2, loopForever) {
var loopCount = 0;
var looper = function () {
if (!loopForever && typeof maxNumLoops !== 'undefined' && loopCount >= maxNumLoops) {
return;
} else {
loopCount += 1;
}
path.attr('d', dPath1)
.transition()
.delay(delayTime)
.duration(duration)
.attr('d', dPath2)
.transition()
.delay(delayTime)
.duration(duration)
.attr('d', dPath1)
.on('end', looper);
};
looper();
}
// helper to loop a path between two points using d3-interpolate-path
function loopPath(path, dPath1, dPath2, pathTextRoot, svg, excludeSegment, loopForever) {
var loopCount = 0;
var looper = function () {
if (!loopForever && typeof maxNumLoops !== 'undefined' && loopCount >= maxNumLoops) {
return;
} else {
loopCount += 1;
}
path.attr('d', dPath1)
.transition()
.delay(delayTime)
.duration(duration)
.attrTween('d', function () {
return d3.interpolatePath(d3.select(this).attr('d'), dPath2, excludeSegment);
})
.on('start', function (a) {
if (pathTextRoot) {
// set timeout in case num points immediately after first tick changes
setTimeout(function () { showPathPoints(svg, d3.transition().duration(duration)); }, 0);
showDValues(pathTextRoot, dPath1, dPath2, this, d3.transition().duration(duration));
}
})
.transition()
.delay(delayTime)
.duration(duration)
.attrTween('d', function () {
return d3.interpolatePath(d3.select(this).attr('d'), dPath1, excludeSegment);
})
.on('start', function (a) {
if (pathTextRoot) {
// set timeout in case num points immediately after first tick changes
setTimeout(function () { showPathPoints(svg, d3.transition().duration(duration)); }, 0);
showDValues(pathTextRoot, dPath1, dPath2, this, d3.transition().duration(duration), true);
}
})
.on('end', looper);
};
looper();
}
function mainExample() {
var dataLine1 = [[0, 0], [200, 100], [400, 50], [500, 80]];
var dataLine2 = [[0, 100], [100, 50], [220, 80], [250, 0],
[300, 20], [350, 30], [400, 100], [420, 10], [430, 90],
[500, 40]];
var data = dataLine1.concat(dataLine2);
var width = 600;
var height = 480;
var lineHeight = 120;
var x = d3.scaleLinear()
.domain(d3.extent(data, function (d) { return d[0]; }))
.range([0, width]);
var y = d3
.scaleLinear()
.domain(d3.extent(data, function (d) { return d[1]; }))
.range([lineHeight * 0.8, lineHeight * 0.5]);
var svg = d3.select('.chart-container').append('svg')
.attr('width', width)
.attr('height', height)
var line = d3.line()
.curve(d3.curveLinear)
.x(function (d) { return x(d[0]); })
.y(function (d) { return y(d[1]); });
var g = svg.append('g');
g.append('path')
.datum(dataLine1)
.attr('d', line);
g.append('text')
.attr('y', 25)
.text('Line A');
g = svg.append('g')
.attr('transform', 'translate(0 ' + lineHeight + ')')
.attr('class', 'using-d3-default');
loopPathBasic(g.append('path'), line(dataLine1), line(dataLine2), true);
g.append('text')
.attr('y', 25)
.text('d3 default interpolation');
g = svg.append('g')
.attr('transform', 'translate(0 ' + lineHeight * 2 + ')')
loopPath(g.append('path'), line(dataLine1), line(dataLine2), null, null, null, true);
g.append('text')
.attr('y', 25)
.text('d3-interpolate-path');
g = svg.append('g')
.attr('transform', 'translate(0 ' + lineHeight * 3 + ')')
g.append('path')
.datum(dataLine2)
.attr('d', line);
g.append('text')
.attr('y', 25)
.text('Line B');
}
var examples = [
{
name: 'cubic simple',
a: 'M20,20 C160,90 90,120 100,160',
b: 'M20,20 C60,90 90,120 150,130 C150,0 180,100 250,100',
scale: false,
},
{
name: 'quadratic simple',
a: 'M0,70 Q160,20 200,100',
b: 'M0,70 Q50,0 100,30 Q120,130 200,100',
},
{
name: 'simple d3-area example',
a: 'M0,42L300,129L300,200L0,200Z',
b: 'M0,77L150,95L300,81L300,200L150,200L0,200Z',
scale: false,
className: 'filled',
excludeSegment: function (a, b) { return a.x === b.x && a.x === 300; },
},
{
name: 'bigger d3-area example',
a: 'M0,100L33,118L67,66L100,154L133,105L167,115L200,62L233,115L267,88L300,103L300,200L267,200L233,200L200,200L167,200L133,200L100,200L67,200L33,200L0,200Z',
b: 'M0,94L75,71L150,138L225,59L300,141L300,200L225,200L150,200L75,200L0,200Z',
scale: false,
className: 'filled',
excludeSegment: function (a, b) { return a.x === b.x && a.x === 300; },
},
{
name: 'shape example',
a: 'M150,0 L280,100 L150,200 L20,100Z',
b: 'M150,10 L230,30 L270,100 L230,170 L150,190 L70,170 L30,100 L70,30Z',
scale: false,
className: 'filled',
},
{
name: 'simple vertical line test',
a: 'M0,0 L300,200',
b: 'M100,20 L150,80 L200,140 L300,200',
scale: false,
},
{
name: 'vertical line test',
a: 'M150,0 L200,40 L100,60 L120,80 L50,100 L250,150 L120,200',
b: 'M120,0 L100,30 L20,45 L220,60 L270,90 L180,95 L50,130 L85,140 L140,150 L190,175 L60,195',
scale: false,
},
{
name: 'curve test',
a: 'M0,32.432432432432506L5.533333333333334,47.39382239382246C11.066666666666668,62.355212355212416,22.133333333333336,92.27799227799233,33.2,108.39768339768345C44.26666666666667,124.51737451737455,55.333333333333336,126.83397683397686,66.39999999999999,136.38996138996143C77.46666666666667,145.94594594594597,88.53333333333335,162.74131274131278,99.59999999999998,156.3706563706564C110.66666666666667,150.00000000000003,121.73333333333335,120.4633204633205,132.8,96.42857142857149C143.86666666666667,72.39382239382245,154.93333333333334,53.861003861003915,166,40.83011583011588C177.0666666666667,27.79922779922784,188.13333333333333,20.2702702702703,199.20000000000002,19.78764478764482C210.26666666666665,19.30501930501934,221.33333333333334,25.86872586872592,232.4,35.328185328185384C243.4666666666667,44.787644787644844,254.5333333333334,57.14285714285719,265.6,71.91119691119695C276.6666666666667,86.67953667953672,287.73333333333335,103.86100386100391,298.8,119.11196911196915C309.8666666666667,134.3629343629344,320.93333333333334,147.68339768339771,332,133.30115830115832C343.06666666666666,118.9189189189189,354.1333333333334,76.8339768339768,365.2,49.99999999999997C376.26666666666665,23.166023166023137,387.3333333333333,11.583011583011569,398.40000000000003,7.046332046332036C409.4666666666667,2.509652509652502,420.5333333333333,5.019305019305004,431.6000000000001,13.6100386100386C442.6666666666667,22.200772200772196,453.7333333333334,36.872586872586886,464.8,55.59845559845562C475.86666666666673,74.32432432432437,486.9333333333334,97.10424710424714,498,109.94208494208497C509.06666666666666,122.7799227799228,520.1333333333333,125.67567567567568,531.2,121.23552123552123C542.2666666666668,116.79536679536677,553.3333333333334,105.01930501930501,564.4,96.71814671814673C575.4666666666667,88.41698841698843,586.5333333333334,83.59073359073363,597.6,93.72586872586878C608.6666666666666,103.86100386100391,619.7333333333332,128.95752895752898,630.8,149.32432432432435C641.8666666666667,169.69111969111972,652.9333333333333,185.32818532818533,664,189.86486486486487C675.0666666666666,194.40154440154438,686.1333333333332,187.83783783783784,697.1999999999999,183.01158301158299C708.2666666666665,178.1853281853282,719.3333333333334,175.09652509652508,730.4,173.45559845559845C741.4666666666667,171.8146718146718,752.5333333333333,171.62162162162164,763.6,159.45945945945948C774.6666666666666,147.29729729729732,785.7333333333332,123.16602316602318,796.7999999999998,109.16988416988418C807.8666666666667,95.17374517374519,818.9333333333334,91.31274131274132,824.4666666666667,89.38223938223939L830,87.45173745173747',
b: 'M0,55.22478736330493L2.194315928618639,59.325637910085C4.388631857237278,63.42648845686508,8.777263714474556,71.62818955042523,13.165895571711836,74.17982989064394C17.55452742894911,76.73147023086267,21.943159286186386,73.63304981773996,26.331791143423658,73.35965978128796C30.720423000660944,73.08626974483597,35.10905485789822,75.63791008505468,39.497686715135494,72.90400972053463C43.88631857237277,70.17010935601458,48.274950429610044,62.15066828675575,52.663582286847316,53.94896719319561C57.052214144084616,45.74726609963545,61.44084600132189,37.36330498177398,65.82947785855914,28.341433778857823C70.21810971579642,19.31956257594167,74.60674157303372,9.659781287970835,78.99537343027099,79.82989064398542C83.38400528750826,150,87.77263714474554,300,92.16126900198282,375C96.54990085922009,450,100.93853271645739,450,105.32716457369463,450C109.71579643093196,450,114.10442828816923,450,118.4930601454065,450C122.88169200264377,450,127.27032385988105,450,131.6589557171183,450C136.04758757435556,450,140.43621943159283,450,144.82485128883013,450C149.21348314606743,450,153.6021150033047,450,157.99074686054198,450C162.37937871777925,450,166.76801057501652,450,171.15664243225382,450C175.5452742894911,450,179.93390614672836,450,184.32253800396563,450C188.7111698612029,450,193.09980171844018,450,197.4884335756775,385.2065613608749C201.87706543291478,320.4131227217497,206.26569729015205,190.8262454434994,210.65432914738926,127.03523693803159C215.0429610046266,63.244228432563794,219.4315928618638,65.24908869987847,223.82022471910113,73.90643985419194C228.20885657633835,82.56379100850542,232.59748843357568,97.87363304981771,236.986120290813,109.35601458080191C241.37475214805022,120.83839611178614,245.76338400528755,128.4933171324423,250.15201586252476,126.03280680437426C254.5406477197621,123.57229647630619,258.9292795769993,110.99635479951398,263.3179114342366,97.32685297691371C267.7065432914739,83.65735115431347,272.0951751487112,68.89428918590521,276.48380700594845,61.512758201701075C280.8724388631857,54.13122721749693,285.261070720423,54.13122721749693,289.64970257766026,54.04009720534626C294.03833443489754,53.948967193195585,298.42696629213486,53.76670716889424,302.81559814937214,53.49331713244223C307.2042300066094,53.21992709599024,311.5928618638467,52.85540704738757,315.98149372108395,52.03523693803155C320.3701255783212,51.21506682867554,324.7587574355585,49.939246658566184,329.14738929279576,51.76184690157955C333.53602115003304,53.58444714459292,337.9246530072703,58.505467800729015,342.3132848645076,73.63304981773996C346.7019167217448,88.7606318347509,351.0905485789821,114.09477521263669,355.4791804362194,132.04738760631835C359.86781229345667,150,364.256444150694,160.57108140947753,368.64507600793127,162.30255164034023C373.03370786516854,164.03402187120292,377.42233972240575,156.9258809234508,381.8109715796431,149.81773997569866C386.19960343688035,142.70959902794652,390.5882352941176,135.6014580801944,394.97686715135495,128.58444714459293C399.3654990085922,121.56743620899147,403.7541308658295,114.64155528554068,408.1427627230668,103.52369380315913C412.5313945803041,92.40583232077762,416.9200264375413,77.09599027946535,421.3086582947787,80.92345078979342C425.6972901520159,84.7509113001215,430.0859220092531,107.7156743620899,434.4745538664904,131.318347509113C438.86318572372767,154.92102065613608,443.2518175809649,179.16160388821382,447.6404494382022,184.81166464155527C452.0290812954395,190.46172539489672,456.41771315267675,177.5212636695018,460.8063450099141,152.09599027946535C465.19497686715135,126.6707168894289,469.5836087243886,88.76063183475092,474.063670411985,67.61846901579587C478.5437320995814,46.476306196840824,483.1152236175369,42.102065613608715,487.5952853051333,42.92223572296473C492.0753469927297,43.74240583232074,496.46397884996696,49.75698663426488,500.8526107072042,66.88942891859053C505.24124256444156,84.02187120291619,509.6298744216788,112.27217496962335,514.0185062789161,127.49088699878496C518.4071381361533,142.70959902794655,522.7957699933908,144.8967193195626,527.184401850628,153.91859052247875C531.5730337078652,162.9404617253949,535.9616655651025,178.7970838396112,540.3502974223397,172.78250303766706C544.738929279577,166.76792223572292,549.1275611368143,138.88213851761842,553.5161929940515,116.19076549210202C557.9048248512887,93.49939246658563,562.2934567085262,76.00243013365734,566.6820885657634,63.69987849331713C571.0707204230007,51.397326852976924,575.4593522802379,44.289185905224805,579.8479841374752,43.83353584447147C584.2366159947125,43.377885783718135,588.6252478519497,49.57472660996357,593.013879709187,58.50546780072906C597.4025115664243,67.43620899149454,601.7911434236615,79.10085054678007,606.1797752808989,93.0437424058323C610.5684071381362,106.98663426488456,614.9570389953734,123.20777642770351,619.3456708526107,137.6063183475091C623.7343027098481,152.0048602673147,628.1229345670853,164.58080194410695,632.5115664243225,151.00243013365738C636.9001982815598,137.4240583232078,641.288830138797,97.6913730255164,645.6774619960344,72.3572296476306C650.0660938532716,47.023086269744816,654.454725710509,36.087484811664616,658.8433575677462,31.8043742405832C663.2319894249835,27.52126366950178,667.6206212822208,29.890643985419143,672.009253139458,38.00121506682863C676.3978849966953,46.11178614823812,680.7865168539325,59.96354799513974,685.17514871117,77.6427703523694C689.5637805684072,95.32199270959906,693.9524124256444,116.82867557715677,698.3410442828817,128.94896719319564C702.7296761401191,141.0692588092345,707.1183079973563,143.80315917375452,711.5069398545935,139.61117861482379C715.8955717118309,135.41919805589302,720.2842035690682,124.3013365735115,724.6728354263054,116.46415552855404C729.0614672835427,108.62697448359658,733.4500991407799,104.07047387606316,737.8387309980171,113.63912515188333C742.2273628552545,123.2077764277035,746.6159947124917,146.90157958687723,751.0046265697289,166.13001215066825C755.3932584269663,185.35844471445924,759.7818902842035,200.12150668286753,764.1705221414408,204.40461725394894C768.5591539986781,208.68772782503038,772.9477858559153,202.49088699878493,777.3364177131526,197.93438639125148C781.7250495703898,193.37788578371809,786.1136814276273,190.46172539489672,790.5023132848645,188.91251518833533C794.8909451421018,187.36330498177395,799.279576999339,187.18104495747264,803.6682088565764,175.69866342648842C808.0568407138136,164.21628189550424,812.4454725710508,141.43377885783715,816.8341044282882,128.21992709599024C821.2227362855255,115.00607533414336,825.6113681427627,111.36087484811662,827.8056840713813,109.53827460510325L830,107.71567436208989',
},
{
name: 'line extends example',
a: 'M0,81L13,128L27,84L40,83L53,114L67,114L80,137L93,116L107,95L120,57L133,87L147,93L160,163L173,95L187,123L200,113',
b: 'M0,81L13,128L27,84L40,83L53,114L67,114L80,137L93,116L107,95L120,57L133,87L147,93L160,163L173,95L187,123L200,113L210,96L228,145L246,92L264,106L282,56L300,90',
scale: false,
},
{
name: 'graticule test',
a: 'M325.1483457087596,531.4452502639945L340.7606028278758,399.7423780391654L359.3445610837574,268.6082938654016L380.395962152234,138.02316901947256L403.36162136396405,7.912231358580129',
b: 'M354.49306197556837,528.5099972371023L344.61144068364655,289.8103838246071L333.8706761621328,30.357378766024',
},
{
name: 'line to line: len(A) = len(b)',
a: 'M0,0L10,10L100,100',
b: 'M10,10L20,20L200,200',
},
{
name: 'line to line: len(A) > len(b)',
a: 'M0,0L10,10L100,100',
b: 'M10,10L20,20',
},
{
name: 'line to line: len(A) < len(b)',
a: 'M0,0L10,10',
b: 'M10,10L20,20L200,200',
},
{
name: 'line to line: len(A)=1',
a: 'M0,0Z',
b: 'M10,10L20,20L200,200',
},
{
name: 'line to line: len(B)=1',
a: 'M0,0L10,10L100,100',
b: 'M10,10Z',
},
{
name: 'line to line: A is null',
a: null,
b: 'M10,10L20,20L200,200',
},
{
name: 'line to line: B is null',
a: 'M0,0L10,10L100,100',
b: null,
},
{
name: 'line to line: A is null and B is null',
a: null,
b: null,
},
{
name: 'where both A and B end in Z',
a: 'M0,0Z',
b: 'M10,10L20,20Z',
},
{
name: 'where A=null, B ends in Z',
a: null,
b: 'M10,10L20,20Z',
},
{
name: 'with other valid `d` characters',
a: 'M0,0m0,0L0,0l0,0H0V0Q0,0,0,0q0,0,0,0C0,0,0,0,0,0c0,0,0,0,0,0T0,0t0,0' +
'S0,0,0,0s0,0,0,0A0,0,0,0,0,0,0',
b: 'M4,4m4,4L4,4l4,4H4V4Q4,4,4,4q4,4,4,4C4,4,4,4,4,4c4,4,4,4,4,4T4,4t4,4' +
'S4,4,4,4s4,4,4,4A4,4,0,0,0,4,4',
},
{
name: 'converts points in A to match types in B',
a: 'M2,2 L3,3 C4,4,4,4,4,4 C5,5,5,5,5,5 L6,6 L7,7',
b: 'M4,4 C5,5,5,5,5,5 L6,6 S7,7,7,7 H8 V9',
},
{
name: 'curves of different length',
a: 'M0,0L3,3C1,1,2,2,4,4C3,3,4,4,6,6L8,0',
b: 'M2,2L3,3C5,5,6,6,4,4C6,6,7,7,5,5C8,8,9,9,6,6C10,10,11,11,7,7L8,8',
},
{
name: 'adds to the closest point',
a: 'M0,0L4,0L20,0',
b: 'M0,4L1,4L3,0L4,0L10,0L14,0L18,0',
},
{
name: 'handles the case where path commands are followed by a space',
a: 'M 0 0 L 10 10 L 100 100',
b: 'M10,10L20,20',
},
{
name: 'includes M when extending if it is the only item',
a: 'M0,0',
b: 'M10,10L20,20L30,30',
},
{
name: 'handles negative numbers properly',
a: 'M0,0L0,0',
b: 'M-10,-10L20,20',
},
];
function formatDString(str) {
return (str || '').split(/(?=[MLCSTQAHV])/gi).join('<br>');
}
function showPathPoints(svg, transition) {
if (!optionShowPathPoints) {
return;
}
var path = svg.select('path');
var points = path.attr('d').split(/[MLCSTQAHVZ\s]/gi)
.filter(function (d) { return d; })
.map(function (d) { return d.split(',').map(function (x) { return +x; }); });
var binding = svg.selectAll('circle').data(points);
var entering = binding.enter().append('circle')
.attr('r', 5)
.style('fill', '#b0b')
.style('fill-opacity', 0.2)
.style('stroke', '#b0b');
binding = binding.merge(entering)
.attr('cx', function (d) { return d[0]; })
.attr('cy', function (d) { return d[1]; });
if (transition) {
binding.transition(transition)
.tween('cx cy', function (d) {
var node = d3.select(this), i = points.indexOf(d);
return function (t) {
var currPoints = path.attr('d').split(/[MLCSTQAHVZ\s]/gi)
.filter(function (d) { return d; })
.map(function (d) { return d.split(',').map(function (x) { return +x; }); });
if (!currPoints[i]) {
node.remove();
} else {
node.attr('cx', currPoints[i][0]);
node.attr('cy', currPoints[i][1]);
}
};
});
}
}
function showDValues(root, dLine1, dLine2, pathNode, transition) {
if (!showPathValues) {
return;
}
root.select('.path-d-original').html(formatDString(dLine1));
root.select('.path-d-end').html(formatDString(dLine2));
// var current = root.select('.path-d').html(formatDString(dLine1));
var currentD = d3.select(pathNode).attr('d');
var current = root.select('.path-d').html(formatDString(currentD));
if (transition) {
var first = true;
current.transition(transition)
.tween('text', function () {
var node = this, i = d3.interpolateString(dLine1, dLine2);
return function (t) {
if (first || (t > 0.05 && Math.floor(t * 100) % 10 === 0)) {
first = false;
// console.log(d3.select(pathNode).attr('d'), t);
}
node.innerHTML = formatDString(d3.select(pathNode).attr('d'));
};
});
}
}
function pathStringToExtent(str) {
var asNumbers = str.replace(/([A-Z])/gi, ' ')
.replace(/\s+/g, ',')
.replace(/,,/g, ',')
.replace(/^,/, '')
.split(',')
.map(function (d) { return +d; })
.filter(function (d) { return !isNaN(d); });
return d3.extent(asNumbers);
}
function makeExample(d, useInterpolatePath) {
var width = exampleWidth;
var height = exampleHeight;
var container = d3.select(this).append('div')
.classed('example-container', true)
.classed('using-d3-interpolate-path', useInterpolatePath)
.classed('using-d3-default', !useInterpolatePath);
// set the title
container.append('h4').text(d.name);
container.append('div').attr('class', 'interpolator-used')
.text(useInterpolatePath ? 'd3-interpolate-path' : 'd3 default interpolation');
// scale the paths to fit nicely in the box
var extent = pathStringToExtent(d.a + ' ' + d.b);
var scaleFactorWidth = Math.min(1, width / extent[1]);
var scaleFactorHeight = Math.min(1, height / extent[1]);
// add the SVG
var svg = container.append('svg')
.attr('width', width)
.attr('height', height)
.append('g');
if (d.scale !== false) {
svg.attr('transform', 'scale(' + scaleFactorWidth + ' ' + scaleFactorHeight + ')');
} else {
svg.attr('transform', 'scale(' + scaleFactorWidth + ')');
}
// adjust the stroke for the scale factor
var strokeWidth = 1.5 / Math.min(scaleFactorWidth, scaleFactorHeight);
var path = svg.append('path')
.style('stroke-width', strokeWidth)
.attr('class', d.className);
// add in the Path text
if (showPathValues) {
var pathTextRoot = container.append('div');
pathTextRoot.html(
'<div class="path-d-string">' +
'<b>Path A</b>' +
'<div class="path-d-original"></div>' +
'</div>' +
'<div class="path-d-string">' +
'<b>Current <code>d</code></b>' +
'<div class="path-d"></div>' +
'</div>' +
'<div class="path-d-string">' +
'<b>Path B</b>' +
'<div class="path-d-end"></div>' +
'</div>');
}
if (useInterpolatePath) {
loopPath(path, d.a, d.b, pathTextRoot, svg, d.excludeSegment);
} else {
loopPathBasic(path, d.a, d.b);
}
showDValues(pathTextRoot, d.a, d.b, path.node());
showPathPoints(svg);
}
var showExamples = examples.filter(function (d, i) {
return typeof activeExamples === 'undefined' || activeExamples.includes(i);
});
// Initialize main example area
if (showMainExample) {
mainExample();
}
// Initialize example grid
var root = d3.select('.examples');
var selection = root.selectAll('.example')
.data(showExamples)
.enter();
selection.append('div')
.attr('class', 'example')
// .style('width', exampleWidth + 'px')
.each(function (d) {
makeExample.call(this, d, true);
makeExample.call(this, d, false);
});
================================================
FILE: docs/index.html
================================================
<!DOCTYPE html>
<html>
<head>
<title>d3-interpolate-path</title>
<meta charset="utf-8">
<link rel="stylesheet" href="example.css" />
</head>
<body>
<h1 class="main-header">d3-interpolate-path</h1>
<p class="main-link"><a href="https://github.com/pbeshai/d3-interpolate-path">https://github.com/pbeshai/d3-interpolate-path</a></p>
<p>
d3-interpolate-path is a <a href="https://d3js.org/">D3</a> plugin that adds an
<a href="https://github.com/d3/d3-interpolate">interpolator</a>
optimized for SVG <path> elements. See the <a href="https://github.com/pbeshai/d3-interpolate-path">GitHub page</a>
for details on usage.
</p>
<p>
A previous version of the library is currently <a href="https://github.com/pbeshai/d3-interpolate-path/issues/15">better at extending lines</a>.
See <a href="v1/">how the older v1.1.1 performs</a>.
</p>
<div class='chart-container'></div>
<p>
<a href="https://github.com/pbeshai/d3-interpolate-path">Code on GitHub</a>
</p>
<h3>Visual Test Examples</h3>
<div class='examples'>
</div>
<script src="//d3js.org/d3.v4.min.js"></script>
<script src="d3-interpolate-path.js"></script>
<script src="examples.js"></script>
</body>
</html>
================================================
FILE: docs/v1/d3-interpolate-path-v1.1.1.js
================================================
(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';
/**
* 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 = Object.assign({}, 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 });
})));
================================================
FILE: docs/v1/example.css
================================================
.main-header {
margin: 0 0 4px;
}
.main-link a {
color: #888;
}
.main-link {
margin-top: 0;
}
body {
font: 14px sans-serif;
padding: 15px;
}
.description {
max-width: 800px;
}
path {
stroke: #0bb;
stroke-width: 1.5px;
fill: none;
}
path.filled {
fill: #0bb;
fill-opacity: 0.2;
}
.example {
display: inline-block;
margin-right: 10px;
margin-bottom: 10px;
border: 1px solid #ccc;
vertical-align: top;
}
.example h4 {
margin: 0;
}
.path-d-string {
width: 80px;
display: inline-block;
overflow: hidden;
margin-right: 15px;
margin-top: 8px;
font-size: 12px;
font-family: sans-serif;
vertical-align: top;
}
.status, .status button {
font-size: 20px;
margin-bottom: 10px;
}
.example-container {
display: inline-block;
padding: 8px;
}
.using-d3-default {
background: #eee;
color: #666;
}
.using-d3-default h4 {
font-weight: 400;
}
.using-d3-default path {
stroke: #b1a776;
}
.using-d3-default path.filled {
fill: #bcb38b;
}
.interpolator-used {
color: #666;
font-size: 0.8em;
}
================================================
FILE: docs/v1/examples.js
================================================
/**
* Apologies for this code. It's kind of hacked together to quickly demonstrate things.
*/
var version = 'v1.1.1';
var exampleWidth = 250;
var exampleHeight = 200;
var showMainExample = !window.location.search.includes('showMainExample=0');
var showPathValues = window.location.search.includes('showPathValues=1');
var optionShowPathPoints = window.location.search.includes('showPathPoints=1');
var maxNumLoops = 10; // comment out for infinite looping
// var activeExamples = [13]; // comment out for all examples
// var activeExamples = [2]; // comment out for all examples
var delayTime = 0; // 1000
var duration = 3000; // 2000
console.log('Show Main Example', showMainExample);
console.log('Show Path Values', showPathValues);
// helper to loop a path between two points
function loopPathBasic(path, dPath1, dPath2, loopForever) {
var loopCount = 0;
var looper = function () {
if (!loopForever && typeof maxNumLoops !== 'undefined' && loopCount >= maxNumLoops) {
return;
} else {
loopCount += 1;
}
path.attr('d', dPath1)
.transition()
.delay(delayTime)
.duration(duration)
.attr('d', dPath2)
.transition()
.delay(delayTime)
.duration(duration)
.attr('d', dPath1)
.on('end', looper);
};
looper();
}
// helper to loop a path between two points using d3-interpolate-path
function loopPath(path, dPath1, dPath2, pathTextRoot, svg, excludeSegment, loopForever) {
var loopCount = 0;
var looper = function () {
if (!loopForever && typeof maxNumLoops !== 'undefined' && loopCount >= maxNumLoops) {
return;
} else {
loopCount += 1;
}
path.attr('d', dPath1)
.transition()
.delay(delayTime)
.duration(duration)
.attrTween('d', function () {
return d3.interpolatePath(d3.select(this).attr('d'), dPath2, excludeSegment);
})
.on('start', function (a) {
if (pathTextRoot) {
// set timeout in case num points immediately after first tick changes
setTimeout(function () { showPathPoints(svg, d3.transition().duration(duration)); }, 0);
showDValues(pathTextRoot, dPath1, dPath2, this, d3.transition().duration(duration));
}
})
.transition()
.delay(delayTime)
.duration(duration)
.attrTween('d', function () {
return d3.interpolatePath(d3.select(this).attr('d'), dPath1, excludeSegment);
})
.on('start', function (a) {
if (pathTextRoot) {
// set timeout in case num points immediately after first tick changes
setTimeout(function () { showPathPoints(svg, d3.transition().duration(duration)); }, 0);
showDValues(pathTextRoot, dPath1, dPath2, this, d3.transition().duration(duration), true);
}
})
.on('end', looper);
};
looper();
}
function mainExample() {
var dataLine1 = [[0, 0], [200, 100], [400, 50], [500, 80]];
var dataLine2 = [[0, 100], [100, 50], [220, 80], [250, 0],
[300, 20], [350, 30], [400, 100], [420, 10], [430, 90],
[500, 40]];
var data = dataLine1.concat(dataLine2);
var width = 600;
var height = 480;
var lineHeight = 120;
var x = d3.scaleLinear()
.domain(d3.extent(data, function (d) { return d[0]; }))
.range([0, width]);
var y = d3
.scaleLinear()
.domain(d3.extent(data, function (d) { return d[1]; }))
.range([lineHeight * 0.8, lineHeight * 0.5]);
var svg = d3.select('.chart-container').append('svg')
.attr('width', width)
.attr('height', height)
var line = d3.line()
.curve(d3.curveLinear)
.x(function (d) { return x(d[0]); })
.y(function (d) { return y(d[1]); });
var g = svg.append('g');
g.append('path')
.datum(dataLine1)
.attr('d', line);
g.append('text')
.attr('y', 25)
.text('Line A');
g = svg.append('g')
.attr('transform', 'translate(0 ' + lineHeight + ')')
.attr('class', 'using-d3-default');
loopPathBasic(g.append('path'), line(dataLine1), line(dataLine2), true);
g.append('text')
.attr('y', 25)
.text('d3 default interpolation');
g = svg.append('g')
.attr('transform', 'translate(0 ' + lineHeight * 2 + ')')
loopPath(g.append('path'), line(dataLine1), line(dataLine2), null, null, null, true);
g.append('text')
.attr('y', 25)
.text('d3-interpolate-path ' + version);
g = svg.append('g')
.attr('transform', 'translate(0 ' + lineHeight * 3 + ')')
g.append('path')
.datum(dataLine2)
.attr('d', line);
g.append('text')
.attr('y', 25)
.text('Line B');
}
var examples = [
{
name: 'cubic simple',
a: 'M20,20 C160,90 90,120 100,160',
b: 'M20,20 C60,90 90,120 150,130 C150,0 180,100 250,100',
scale: false,
},
{
name: 'quadratic simple',
a: 'M0,70 Q160,20 200,100',
b: 'M0,70 Q50,0 100,30 Q120,130 200,100',
},
{
name: 'simple d3-area example',
a: 'M0,42L300,129L300,200L0,200Z',
b: 'M0,77L150,95L300,81L300,200L150,200L0,200Z',
scale: false,
className: 'filled',
excludeSegment: function (a, b) { return a.x === b.x && a.x === 300; },
},
{
name: 'bigger d3-area example',
a: 'M0,100L33,118L67,66L100,154L133,105L167,115L200,62L233,115L267,88L300,103L300,200L267,200L233,200L200,200L167,200L133,200L100,200L67,200L33,200L0,200Z',
b: 'M0,94L75,71L150,138L225,59L300,141L300,200L225,200L150,200L75,200L0,200Z',
scale: false,
className: 'filled',
excludeSegment: function (a, b) { return a.x === b.x && a.x === 300; },
},
{
name: 'shape example',
a: 'M150,0 L280,100 L150,200 L20,100Z',
b: 'M150,10 L230,30 L270,100 L230,170 L150,190 L70,170 L30,100 L70,30Z',
scale: false,
className: 'filled',
},
{
name: 'simple vertical line test',
a: 'M0,0 L300,200',
b: 'M100,20 L150,80 L200,140 L300,200',
scale: false,
},
{
name: 'vertical line test',
a: 'M150,0 L200,40 L100,60 L120,80 L50,100 L250,150 L120,200',
b: 'M120,0 L100,30 L20,45 L220,60 L270,90 L180,95 L50,130 L85,140 L140,150 L190,175 L60,195',
scale: false,
},
{
name: 'curve test',
a: 'M0,32.432432432432506L5.533333333333334,47.39382239382246C11.066666666666668,62.355212355212416,22.133333333333336,92.27799227799233,33.2,108.39768339768345C44.26666666666667,124.51737451737455,55.333333333333336,126.83397683397686,66.39999999999999,136.38996138996143C77.46666666666667,145.94594594594597,88.53333333333335,162.74131274131278,99.59999999999998,156.3706563706564C110.66666666666667,150.00000000000003,121.73333333333335,120.4633204633205,132.8,96.42857142857149C143.86666666666667,72.39382239382245,154.93333333333334,53.861003861003915,166,40.83011583011588C177.0666666666667,27.79922779922784,188.13333333333333,20.2702702702703,199.20000000000002,19.78764478764482C210.26666666666665,19.30501930501934,221.33333333333334,25.86872586872592,232.4,35.328185328185384C243.4666666666667,44.787644787644844,254.5333333333334,57.14285714285719,265.6,71.91119691119695C276.6666666666667,86.67953667953672,287.73333333333335,103.86100386100391,298.8,119.11196911196915C309.8666666666667,134.3629343629344,320.93333333333334,147.68339768339771,332,133.30115830115832C343.06666666666666,118.9189189189189,354.1333333333334,76.8339768339768,365.2,49.99999999999997C376.26666666666665,23.166023166023137,387.3333333333333,11.583011583011569,398.40000000000003,7.046332046332036C409.4666666666667,2.509652509652502,420.5333333333333,5.019305019305004,431.6000000000001,13.6100386100386C442.6666666666667,22.200772200772196,453.7333333333334,36.872586872586886,464.8,55.59845559845562C475.86666666666673,74.32432432432437,486.9333333333334,97.10424710424714,498,109.94208494208497C509.06666666666666,122.7799227799228,520.1333333333333,125.67567567567568,531.2,121.23552123552123C542.2666666666668,116.79536679536677,553.3333333333334,105.01930501930501,564.4,96.71814671814673C575.4666666666667,88.41698841698843,586.5333333333334,83.59073359073363,597.6,93.72586872586878C608.6666666666666,103.86100386100391,619.7333333333332,128.95752895752898,630.8,149.32432432432435C641.8666666666667,169.69111969111972,652.9333333333333,185.32818532818533,664,189.86486486486487C675.0666666666666,194.40154440154438,686.1333333333332,187.83783783783784,697.1999999999999,183.01158301158299C708.2666666666665,178.1853281853282,719.3333333333334,175.09652509652508,730.4,173.45559845559845C741.4666666666667,171.8146718146718,752.5333333333333,171.62162162162164,763.6,159.45945945945948C774.6666666666666,147.29729729729732,785.7333333333332,123.16602316602318,796.7999999999998,109.16988416988418C807.8666666666667,95.17374517374519,818.9333333333334,91.31274131274132,824.4666666666667,89.38223938223939L830,87.45173745173747',
b: 'M0,55.22478736330493L2.194315928618639,59.325637910085C4.388631857237278,63.42648845686508,8.777263714474556,71.62818955042523,13.165895571711836,74.17982989064394C17.55452742894911,76.73147023086267,21.943159286186386,73.63304981773996,26.331791143423658,73.35965978128796C30.720423000660944,73.08626974483597,35.10905485789822,75.63791008505468,39.497686715135494,72.90400972053463C43.88631857237277,70.17010935601458,48.274950429610044,62.15066828675575,52.663582286847316,53.94896719319561C57.052214144084616,45.74726609963545,61.44084600132189,37.36330498177398,65.82947785855914,28.341433778857823C70.21810971579642,19.31956257594167,74.60674157303372,9.659781287970835,78.99537343027099,79.82989064398542C83.38400528750826,150,87.77263714474554,300,92.16126900198282,375C96.54990085922009,450,100.93853271645739,450,105.32716457369463,450C109.71579643093196,450,114.10442828816923,450,118.4930601454065,450C122.88169200264377,450,127.27032385988105,450,131.6589557171183,450C136.04758757435556,450,140.43621943159283,450,144.82485128883013,450C149.21348314606743,450,153.6021150033047,450,157.99074686054198,450C162.37937871777925,450,166.76801057501652,450,171.15664243225382,450C175.5452742894911,450,179.93390614672836,450,184.32253800396563,450C188.7111698612029,450,193.09980171844018,450,197.4884335756775,385.2065613608749C201.87706543291478,320.4131227217497,206.26569729015205,190.8262454434994,210.65432914738926,127.03523693803159C215.0429610046266,63.244228432563794,219.4315928618638,65.24908869987847,223.82022471910113,73.90643985419194C228.20885657633835,82.56379100850542,232.59748843357568,97.87363304981771,236.986120290813,109.35601458080191C241.37475214805022,120.83839611178614,245.76338400528755,128.4933171324423,250.15201586252476,126.03280680437426C254.5406477197621,123.57229647630619,258.9292795769993,110.99635479951398,263.3179114342366,97.32685297691371C267.7065432914739,83.65735115431347,272.0951751487112,68.89428918590521,276.48380700594845,61.512758201701075C280.8724388631857,54.13122721749693,285.261070720423,54.13122721749693,289.64970257766026,54.04009720534626C294.03833443489754,53.948967193195585,298.42696629213486,53.76670716889424,302.81559814937214,53.49331713244223C307.2042300066094,53.21992709599024,311.5928618638467,52.85540704738757,315.98149372108395,52.03523693803155C320.3701255783212,51.21506682867554,324.7587574355585,49.939246658566184,329.14738929279576,51.76184690157955C333.53602115003304,53.58444714459292,337.9246530072703,58.505467800729015,342.3132848645076,73.63304981773996C346.7019167217448,88.7606318347509,351.0905485789821,114.09477521263669,355.4791804362194,132.04738760631835C359.86781229345667,150,364.256444150694,160.57108140947753,368.64507600793127,162.30255164034023C373.03370786516854,164.03402187120292,377.42233972240575,156.9258809234508,381.8109715796431,149.81773997569866C386.19960343688035,142.70959902794652,390.5882352941176,135.6014580801944,394.97686715135495,128.58444714459293C399.3654990085922,121.56743620899147,403.7541308658295,114.64155528554068,408.1427627230668,103.52369380315913C412.5313945803041,92.40583232077762,416.9200264375413,77.09599027946535,421.3086582947787,80.92345078979342C425.6972901520159,84.7509113001215,430.0859220092531,107.7156743620899,434.4745538664904,131.318347509113C438.86318572372767,154.92102065613608,443.2518175809649,179.16160388821382,447.6404494382022,184.81166464155527C452.0290812954395,190.46172539489672,456.41771315267675,177.5212636695018,460.8063450099141,152.09599027946535C465.19497686715135,126.6707168894289,469.5836087243886,88.76063183475092,474.063670411985,67.61846901579587C478.5437320995814,46.476306196840824,483.1152236175369,42.102065613608715,487.5952853051333,42.92223572296473C492.0753469927297,43.74240583232074,496.46397884996696,49.75698663426488,500.8526107072042,66.88942891859053C505.24124256444156,84.02187120291619,509.6298744216788,112.27217496962335,514.0185062789161,127.49088699878496C518.4071381361533,142.70959902794655,522.7957699933908,144.8967193195626,527.184401850628,153.91859052247875C531.5730337078652,162.9404617253949,535.9616655651025,178.7970838396112,540.3502974223397,172.78250303766706C544.738929279577,166.76792223572292,549.1275611368143,138.88213851761842,553.5161929940515,116.19076549210202C557.9048248512887,93.49939246658563,562.2934567085262,76.00243013365734,566.6820885657634,63.69987849331713C571.0707204230007,51.397326852976924,575.4593522802379,44.289185905224805,579.8479841374752,43.83353584447147C584.2366159947125,43.377885783718135,588.6252478519497,49.57472660996357,593.013879709187,58.50546780072906C597.4025115664243,67.43620899149454,601.7911434236615,79.10085054678007,606.1797752808989,93.0437424058323C610.5684071381362,106.98663426488456,614.9570389953734,123.20777642770351,619.3456708526107,137.6063183475091C623.7343027098481,152.0048602673147,628.1229345670853,164.58080194410695,632.5115664243225,151.00243013365738C636.9001982815598,137.4240583232078,641.288830138797,97.6913730255164,645.6774619960344,72.3572296476306C650.0660938532716,47.023086269744816,654.454725710509,36.087484811664616,658.8433575677462,31.8043742405832C663.2319894249835,27.52126366950178,667.6206212822208,29.890643985419143,672.009253139458,38.00121506682863C676.3978849966953,46.11178614823812,680.7865168539325,59.96354799513974,685.17514871117,77.6427703523694C689.5637805684072,95.32199270959906,693.9524124256444,116.82867557715677,698.3410442828817,128.94896719319564C702.7296761401191,141.0692588092345,707.1183079973563,143.80315917375452,711.5069398545935,139.61117861482379C715.8955717118309,135.41919805589302,720.2842035690682,124.3013365735115,724.6728354263054,116.46415552855404C729.0614672835427,108.62697448359658,733.4500991407799,104.07047387606316,737.8387309980171,113.63912515188333C742.2273628552545,123.2077764277035,746.6159947124917,146.90157958687723,751.0046265697289,166.13001215066825C755.3932584269663,185.35844471445924,759.7818902842035,200.12150668286753,764.1705221414408,204.40461725394894C768.5591539986781,208.68772782503038,772.9477858559153,202.49088699878493,777.3364177131526,197.93438639125148C781.7250495703898,193.37788578371809,786.1136814276273,190.46172539489672,790.5023132848645,188.91251518833533C794.8909451421018,187.36330498177395,799.279576999339,187.18104495747264,803.6682088565764,175.69866342648842C808.0568407138136,164.21628189550424,812.4454725710508,141.43377885783715,816.8341044282882,128.21992709599024C821.2227362855255,115.00607533414336,825.6113681427627,111.36087484811662,827.8056840713813,109.53827460510325L830,107.71567436208989',
},
{
name: 'line extends example',
a: 'M0,81L13,128L27,84L40,83L53,114L67,114L80,137L93,116L107,95L120,57L133,87L147,93L160,163L173,95L187,123L200,113',
b: 'M0,81L13,128L27,84L40,83L53,114L67,114L80,137L93,116L107,95L120,57L133,87L147,93L160,163L173,95L187,123L200,113L210,96L228,145L246,92L264,106L282,56L300,90',
scale: false,
},
{
name: 'graticule test',
a: 'M325.1483457087596,531.4452502639945L340.7606028278758,399.7423780391654L359.3445610837574,268.6082938654016L380.395962152234,138.02316901947256L403.36162136396405,7.912231358580129',
b: 'M354.49306197556837,528.5099972371023L344.61144068364655,289.8103838246071L333.8706761621328,30.357378766024',
},
{
name: 'line to line: len(A) = len(b)',
a: 'M0,0L10,10L100,100',
b: 'M10,10L20,20L200,200',
},
{
name: 'line to line: len(A) > len(b)',
a: 'M0,0L10,10L100,100',
b: 'M10,10L20,20',
},
{
name: 'line to line: len(A) < len(b)',
a: 'M0,0L10,10',
b: 'M10,10L20,20L200,200',
},
{
name: 'line to line: len(A)=1',
a: 'M0,0Z',
b: 'M10,10L20,20L200,200',
},
{
name: 'line to line: len(B)=1',
a: 'M0,0L10,10L100,100',
b: 'M10,10Z',
},
{
name: 'line to line: A is null',
a: null,
b: 'M10,10L20,20L200,200',
},
{
name: 'line to line: B is null',
a: 'M0,0L10,10L100,100',
b: null,
},
{
name: 'line to line: A is null and B is null',
a: null,
b: null,
},
{
name: 'where both A and B end in Z',
a: 'M0,0Z',
b: 'M10,10L20,20Z',
},
{
name: 'where A=null, B ends in Z',
a: null,
b: 'M10,10L20,20Z',
},
{
name: 'with other valid `d` characters',
a: 'M0,0m0,0L0,0l0,0H0V0Q0,0,0,0q0,0,0,0C0,0,0,0,0,0c0,0,0,0,0,0T0,0t0,0' +
'S0,0,0,0s0,0,0,0A0,0,0,0,0,0,0',
b: 'M4,4m4,4L4,4l4,4H4V4Q4,4,4,4q4,4,4,4C4,4,4,4,4,4c4,4,4,4,4,4T4,4t4,4' +
'S4,4,4,4s4,4,4,4A4,4,0,0,0,4,4',
},
{
name: 'converts points in A to match types in B',
a: 'M2,2 L3,3 C4,4,4,4,4,4 C5,5,5,5,5,5 L6,6 L7,7',
b: 'M4,4 C5,5,5,5,5,5 L6,6 S7,7,7,7 H8 V9',
},
{
name: 'curves of different length',
a: 'M0,0L3,3C1,1,2,2,4,4C3,3,4,4,6,6L8,0',
b: 'M2,2L3,3C5,5,6,6,4,4C6,6,7,7,5,5C8,8,9,9,6,6C10,10,11,11,7,7L8,8',
},
{
name: 'adds to the closest point',
a: 'M0,0L4,0L20,0',
b: 'M0,4L1,4L3,0L4,0L10,0L14,0L18,0',
},
{
name: 'handles the case where path commands are followed by a space',
a: 'M 0 0 L 10 10 L 100 100',
b: 'M10,10L20,20',
},
{
name: 'includes M when extending if it is the only item',
a: 'M0,0',
b: 'M10,10L20,20L30,30',
},
{
name: 'handles negative numbers properly',
a: 'M0,0L0,0',
b: 'M-10,-10L20,20',
},
];
function formatDString(str) {
return (str || '').split(/(?=[MLCSTQAHV])/gi).join('<br>');
}
function showPathPoints(svg, transition) {
if (!optionShowPathPoints) {
return;
}
var path = svg.select('path');
var points = path.attr('d').split(/[MLCSTQAHVZ\s]/gi)
.filter(function (d) { return d; })
.map(function (d) { return d.split(',').map(function (x) { return +x; }); });
var binding = svg.selectAll('circle').data(points);
var entering = binding.enter().append('circle')
.attr('r', 5)
.style('fill', '#b0b')
.style('fill-opacity', 0.2)
.style('stroke', '#b0b');
binding = binding.merge(entering)
.attr('cx', function (d) { return d[0]; })
.attr('cy', function (d) { return d[1]; });
if (transition) {
binding.transition(transition)
.tween('cx cy', function (d) {
var node = d3.select(this), i = points.indexOf(d);
return function (t) {
var currPoints = path.attr('d').split(/[MLCSTQAHVZ\s]/gi)
.filter(function (d) { return d; })
.map(function (d) { return d.split(',').map(function (x) { return +x; }); });
if (!currPoints[i]) {
node.remove();
} else {
node.attr('cx', currPoints[i][0]);
node.attr('cy', currPoints[i][1]);
}
};
});
}
}
function showDValues(root, dLine1, dLine2, pathNode, transition) {
if (!showPathValues) {
return;
}
root.select('.path-d-original').html(formatDString(dLine1));
root.select('.path-d-end').html(formatDString(dLine2));
// var current = root.select('.path-d').html(formatDString(dLine1));
var currentD = d3.select(pathNode).attr('d');
var current = root.select('.path-d').html(formatDString(currentD));
if (transition) {
var first = true;
current.transition(transition)
.tween('text', function () {
var node = this, i = d3.interpolateString(dLine1, dLine2);
return function (t) {
if (first || (t > 0.05 && Math.floor(t * 100) % 10 === 0)) {
first = false;
// console.log(d3.select(pathNode).attr('d'), t);
}
node.innerHTML = formatDString(d3.select(pathNode).attr('d'));
};
});
}
}
function pathStringToExtent(str) {
var asNumbers = str.replace(/([A-Z])/gi, ' ')
.replace(/\s+/g, ',')
.replace(/,,/g, ',')
.replace(/^,/, '')
.split(',')
.map(function (d) { return +d; })
.filter(function (d) { return !isNaN(d); });
return d3.extent(asNumbers);
}
function makeExample(d, useInterpolatePath) {
var width = exampleWidth;
var height = exampleHeight;
var container = d3.select(this).append('div')
.classed('example-container', true)
.classed('using-d3-interpolate-path', useInterpolatePath)
.classed('using-d3-default', !useInterpolatePath);
// set the title
container.append('h4').text(d.name);
container.append('div').attr('class', 'interpolator-used')
.text(useInterpolatePath ? ('d3-interpolate-path ' + version) : 'd3 default interpolation');
// scale the paths to fit nicely in the box
var extent = pathStringToExtent(d.a + ' ' + d.b);
var scaleFactorWidth = Math.min(1, width / extent[1]);
var scaleFactorHeight = Math.min(1, height / extent[1]);
// add the SVG
var svg = container.append('svg')
.attr('width', width)
.attr('height', height)
.append('g');
if (d.scale !== false) {
svg.attr('transform', 'scale(' + scaleFactorWidth + ' ' + scaleFactorHeight + ')');
} else {
svg.attr('transform', 'scale(' + scaleFactorWidth + ')');
}
// adjust the stroke for the scale factor
var strokeWidth = 1.5 / Math.min(scaleFactorWidth, scaleFactorHeight);
var path = svg.append('path')
.style('stroke-width', strokeWidth)
.attr('class', d.className);
// add in the Path text
if (showPathValues) {
var pathTextRoot = container.append('div');
pathTextRoot.html(
'<div class="path-d-string">' +
'<b>Path A</b>' +
'<div class="path-d-original"></div>' +
'</div>' +
'<div class="path-d-string">' +
'<b>Current <code>d</code></b>' +
'<div class="path-d"></div>' +
'</div>' +
'<div class="path-d-string">' +
'<b>Path B</b>' +
'<div class="path-d-end"></div>' +
'</div>');
}
if (useInterpolatePath) {
loopPath(path, d.a, d.b, pathTextRoot, svg, d.excludeSegment);
} else {
loopPathBasic(path, d.a, d.b);
}
showDValues(pathTextRoot, d.a, d.b, path.node());
showPathPoints(svg);
}
var showExamples = examples.filter(function (d, i) {
return typeof activeExamples === 'undefined' || activeExamples.includes(i);
});
// Initialize main example area
if (showMainExample) {
mainExample();
}
// Initialize example grid
var root = d3.select('.examples');
var selection = root.selectAll('.example')
.data(showExamples)
.enter();
selection.append('div')
.attr('class', 'example')
// .style('width', exampleWidth + 'px')
.each(function (d) {
makeExample.call(this, d, true);
makeExample.call(this, d, false);
});
================================================
FILE: docs/v1/index.html
================================================
<!DOCTYPE html>
<html>
<head>
<title>d3-interpolate-path v1.1.1</title>
<meta charset="utf-8">
<link rel="stylesheet" href="example.css" />
</head>
<body>
<h1 class="main-header">d3-interpolate-path v1.1.1</h1>
<p class="main-link"><a href="https://github.com/pbeshai/d3-interpolate-path">https://github.com/pbeshai/d3-interpolate-path</a></p>
<p>This example page shows an older version, <b>v1.1.1</b>. See <a href="//peterbeshai.com/d3-interpolate-path">the latest version</a>.</p>
<p>
d3-interpolate-path is a <a href="https://d3js.org/">D3</a> plugin that adds an
<a href="https://github.com/d3/d3-interpolate">interpolator</a>
optimized for SVG <path> elements. See the <a href="https://github.com/pbeshai/d3-interpolate-path">GitHub page</a>
for details on usage.
</p>
<div class='chart-container'></div>
<p>
<a href="https://github.com/pbeshai/d3-interpolate-path">Code on GitHub</a>
</p>
<h3>Visual Test Examples</h3>
<div class='examples'>
</div>
<script src="//d3js.org/d3.v4.min.js"></script>
<script src="d3-interpolate-path-v1.1.1.js"></script>
<script src="examples.js"></script>
</body>
</html>
================================================
FILE: index.js
================================================
export {
default as interpolatePath,
interpolatePathCommands,
pathCommandsFromString,
} from './src/interpolatePath';
================================================
FILE: package.json
================================================
{
"name": "d3-interpolate-path",
"version": "2.3.0",
"description": "Interpolates path `d` attribute smoothly when A and B have different number of points.",
"author": "Peter Beshai <peter.beshai@gmail.com> (http://github.com/pbeshai)",
"keywords": [
"d3",
"d3-module",
"d3-interpolate",
"d3-interpolate-path",
"svg path",
"path animation",
"interpolation",
"canvas path"
],
"license": "BSD-3-Clause",
"main": "build/d3-interpolate-path.js",
"module": "build/d3-interpolate-path.mjs",
"homepage": "https://pbeshai.github.io/d3-interpolate-path",
"repository": {
"type": "git",
"url": "https://github.com/pbeshai/d3-interpolate-path.git"
},
"files": [
"build/"
],
"scripts": {
"build": "rm -rf build && mkdir build && rollup --config rollup.config.js",
"watch": "rollup --config rollup.config.js --watch",
"lint": "eslint --ext .js src --fix",
"pretest": "npm run build",
"test": "tape 'test/**/*-test.js'",
"prepublish": "npm run lint && npm run test && uglifyjs build/d3-interpolate-path.js -c -m -o build/d3-interpolate-path.min.js",
"postpublish": "zip -j build/d3-interpolate-path.zip -- LICENSE README.md build/d3-interpolate-path.js build/d3-interpolate-path.min.js"
},
"devDependencies": {
"@babel/core": "^7.14.0",
"@babel/preset-env": "^7.14.1",
"babel-plugin-transform-object-assign": "^6.22.0",
"babel-runtime": "^6.26.0",
"braces": ">=3.0.2",
"eslint": "^7.26.0",
"eslint-config-prettier": "^8.3.0",
"minimist": ">=1.2.5",
"rollup": "^2.47.0",
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-node-resolve": "^5.0.1",
"tape": "^5.2.2",
"uglify-js": "^3.13.6"
},
"dependencies": {}
}
================================================
FILE: rollup.config.js
================================================
// rollup.config.js
import resolve from 'rollup-plugin-node-resolve';
import babel from 'rollup-plugin-babel';
var globals = {
'd3-interpolate': 'd3',
};
const config = {
input: 'index.js',
plugins: [
resolve(),
babel({
plugins: ['transform-object-assign'],
exclude: 'node_modules/**', // only transpile our source code
}),
],
external: Object.keys(globals),
output: [
// main UMD output
{
file: `build/d3-interpolate-path.js`,
name: 'd3',
format: 'umd',
indent: false,
extend: true,
globals: globals,
},
// copy main UMD output for use on docs site
{
file: `docs/d3-interpolate-path.js`,
name: 'd3',
format: 'umd',
indent: false,
extend: true,
globals: globals,
},
// main ES Modules output
{
file: `build/d3-interpolate-path.mjs`,
name: 'd3',
format: 'es',
indent: false,
extend: true,
globals: globals,
},
],
};
export default config;
================================================
FILE: src/.babelrc
================================================
{
"presets": [
["@babel/env", {"modules": false}]
]
}
================================================
FILE: src/interpolatePath.js
================================================
import splitCurve from './split';
const commandTokenRegex = /[MLCSTQAHVZmlcstqahv]|-?[\d.e+-]+/g;
/**
* List of params for each command type in a path `d` attribute
*/
const 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'],
Z: [],
};
// Add lower case entries too matching uppercase (e.g. 'm' == 'M')
Object.keys(typeMap).forEach((key) => {
typeMap[key.toLowerCase()] = typeMap[key];
});
function arrayOfLength(length, value) {
const array = Array(length);
for (let i = 0; i < length; i++) {
array[i] = value;
}
return array;
}
/**
* 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) {
return `${command.type}${typeMap[command.type]
.map((p) => 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) {
const conversionMap = {
x1: 'x',
y1: 'y',
x2: 'x',
y2: 'y',
};
const readFromBKeys = ['xAxisRotation', 'largeArcFlag', 'sweepFlag'];
// convert (but ignore M types)
if (aCommand.type !== bCommand.type && bCommand.type.toUpperCase() !== 'M') {
const aConverted = {};
Object.keys(bCommand).forEach((bKey) => {
const bValue = bCommand[bKey];
// first read from the A command
let 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;
}
/**
* Interpolate between command objects commandStart and commandEnd segmentCount times.
* If the types are L, Q, or C then the curves are split as per de Casteljau's algorithm.
* Otherwise we just copy commandStart segmentCount - 1 times, finally ending with commandEnd.
*
* @param {Object} commandStart Command object at the beginning of the segment
* @param {Object} commandEnd Command object at the end of the segment
* @param {Number} segmentCount The number of segments to split this into. If only 1
* Then [commandEnd] is returned.
* @return {Object[]} Array of ~segmentCount command objects between commandStart and
* commandEnd. (Can be segmentCount+1 objects if commandStart is type M).
*/
function splitSegment(commandStart, commandEnd, segmentCount) {
let segments = [];
// line, quadratic bezier, or cubic bezier
if (
commandEnd.type === 'L' ||
commandEnd.type === 'Q' ||
commandEnd.type === 'C'
) {
segments = segments.concat(
splitCurve(commandStart, commandEnd, segmentCount)
);
// general case - just copy the same point
} else {
const copyCommand = Object.assign({}, commandStart);
// convert M to L
if (copyCommand.type === 'M') {
copyCommand.type = 'L';
}
segments = segments.concat(
arrayOfLength(segmentCount - 1).map(() => copyCommand)
);
segments.push(commandEnd);
}
return segments;
}
/**
* Extends an array of commandsToExtend to the length of the referenceCommands by
* splitting segments until the number of commands match. Ensures all the actual
* points of commandsToExtend are in the extended array.
*
* @param {Object[]} commandsToExtend The command object array to extend
* @param {Object[]} referenceCommands The command object array to match in length
* @param {Function} excludeSegment a function that takes a start command object and
* end command object and returns true if the segment should be excluded from splitting.
* @return {Object[]} The extended commandsToExtend array
*/
function extend(commandsToExtend, referenceCommands, excludeSegment) {
// compute insertion points:
// number of segments in the path to extend
const numSegmentsToExtend = commandsToExtend.length - 1;
// number of segments in the reference path.
const numReferenceSegments = referenceCommands.length - 1;
// this value is always between [0, 1].
const segmentRatio = numSegmentsToExtend / numReferenceSegments;
// create a map, mapping segments in referenceCommands to how many points
// should be added in that segment (should always be >= 1 since we need each
// point itself).
// 0 = segment 0-1, 1 = segment 1-2, n-1 = last vertex
const countPointsPerSegment = arrayOfLength(numReferenceSegments).reduce(
(accum, d, i) => {
let insertIndex = Math.floor(segmentRatio * i);
// handle excluding segments
if (
excludeSegment &&
insertIndex < commandsToExtend.length - 1 &&
excludeSegment(
commandsToExtend[insertIndex],
commandsToExtend[insertIndex + 1]
)
) {
// set the insertIndex to the segment that this point should be added to:
// round the insertIndex essentially so we split half and half on
// neighbouring segments. hence the segmentRatio * i < 0.5
const addToPriorSegment = (segmentRatio * i) % 1 < 0.5;
// only skip segment if we already have 1 point in it (can't entirely remove a segment)
if (accum[insertIndex]) {
// TODO - Note this is a naive algorithm that should work for most d3-area use cases
// but if two adjacent segments are supposed to be skipped, this will not perform as
// expected. Could be updated to search for nearest segment to place the point in, but
// will only do that if necessary.
// add to the prior segment
if (addToPriorSegment) {
if (insertIndex > 0) {
insertIndex -= 1;
// not possible to add to previous so adding to next
} else if (insertIndex < commandsToExtend.length - 1) {
insertIndex += 1;
}
// add to next segment
} else if (insertIndex < commandsToExtend.length - 1) {
insertIndex += 1;
// not possible to add to next so adding to previous
} else if (insertIndex > 0) {
insertIndex -= 1;
}
}
}
accum[insertIndex] = (accum[insertIndex] || 0) + 1;
return accum;
},
[]
);
// extend each segment to have the correct number of points for a smooth interpolation
const extended = countPointsPerSegment.reduce((extended, segmentCount, i) => {
// if last command, just add `segmentCount` number of times
if (i === commandsToExtend.length - 1) {
const lastCommandCopies = arrayOfLength(
segmentCount,
Object.assign({}, commandsToExtend[commandsToExtend.length - 1])
);
// convert M to L
if (lastCommandCopies[0].type === 'M') {
lastCommandCopies.forEach((d) => {
d.type = 'L';
});
}
return extended.concat(lastCommandCopies);
}
// otherwise, split the segment segmentCount times.
return extended.concat(
splitSegment(commandsToExtend[i], commandsToExtend[i + 1], segmentCount)
);
}, []);
// add in the very first point since splitSegment only adds in the ones after it
extended.unshift(commandsToExtend[0]);
return extended;
}
/**
* Takes a path `d` string and converts it into an array of command
* objects. Drops the `Z` character.
*
* @param {String|null} d A path `d` string
*/
export function pathCommandsFromString(d) {
// split into valid tokens
const tokens = (d || '').match(commandTokenRegex) || [];
const commands = [];
let commandArgs;
let command;
// iterate over each token, checking if we are at a new command
// by presence in the typeMap
for (let i = 0; i < tokens.length; ++i) {
commandArgs = typeMap[tokens[i]];
// new command found:
if (commandArgs) {
command = {
type: tokens[i],
};
// add each of the expected args for this command:
for (let a = 0; a < commandArgs.length; ++a) {
command[commandArgs[a]] = +tokens[i + a + 1];
}
// need to increment our token index appropriately since
// we consumed token args
i += commandArgs.length;
commands.push(command);
}
}
return commands;
}
/**
* 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` command in paths unless both A and B end with it.
*
* This function works directly with arrays of command objects instead of with
* path `d` strings (see interpolatePath for working with `d` strings).
*
* @param {Object[]} aCommandsInput Array of path commands
* @param {Object[]} bCommandsInput Array of path commands
* @param {(Function|Object)} interpolateOptions
* @param {Function} interpolateOptions.excludeSegment a function that takes a start command object and
* end command object and returns true if the segment should be excluded from splitting.
* @param {Boolean} interpolateOptions.snapEndsToInput a boolean indicating whether end of input should
* be sourced from input argument or computed.
* @returns {Function} Interpolation function that maps t ([0, 1]) to an array of path commands.
*/
export function interpolatePathCommands(
aCommandsInput,
bCommandsInput,
interpolateOptions
) {
// make a copy so we don't mess with the input arrays
let aCommands = aCommandsInput == null ? [] : aCommandsInput.slice();
let bCommands = bCommandsInput == null ? [] : bCommandsInput.slice();
const { excludeSegment, snapEndsToInput } =
typeof interpolateOptions === 'object'
? interpolateOptions
: {
excludeSegment: interpolateOptions,
snapEndsToInput: true,
};
// both input sets are empty, so we don't interpolate
if (!aCommands.length && !bCommands.length) {
return function nullInterpolator() {
return [];
};
}
// do we add Z during interpolation? yes if both have it. (we'd expect both to have it or not)
const addZ =
(aCommands.length === 0 || aCommands[aCommands.length - 1].type === 'Z') &&
(bCommands.length === 0 || bCommands[bCommands.length - 1].type === 'Z');
// we temporarily remove Z
if (aCommands.length > 0 && aCommands[aCommands.length - 1].type === 'Z') {
aCommands.pop();
}
if (bCommands.length > 0 && bCommands[bCommands.length - 1].type === 'Z') {
bCommands.pop();
}
// 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 (!aCommands.length) {
aCommands.push(bCommands[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 (!bCommands.length) {
bCommands.push(aCommands[0]);
}
// extend to match equal size
const numPointsToExtend = Math.abs(bCommands.length - aCommands.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, excludeSegment);
// else if A has more points than B, add more points to B
} else if (bCommands.length < aCommands.length) {
bCommands = extend(bCommands, aCommands, excludeSegment);
}
}
// commands have same length now.
// convert commands in A to the same type as those in B
aCommands = aCommands.map((aCommand, i) =>
convertToSameType(aCommand, bCommands[i])
);
// create mutable interpolated command objects
const interpolatedCommands = aCommands.map((aCommand) => ({ ...aCommand }));
if (addZ) {
interpolatedCommands.push({ type: 'Z' });
aCommands.push({ type: 'Z' }); // required for when returning at t == 0
}
return function pathCommandInterpolator(t) {
// at 1 return the final value without the extensions used during interpolation
if (t === 1 && snapEndsToInput) {
return bCommandsInput == null ? [] : bCommandsInput;
}
// work with aCommands directly since interpolatedCommands are mutated
if (t === 0) {
return aCommands;
}
// interpolate the commands using the mutable interpolated command objs
for (let i = 0; i < interpolatedCommands.length; ++i) {
// if (interpolatedCommands[i].type === 'Z') continue;
const aCommand = aCommands[i];
const bCommand = bCommands[i];
const interpolatedCommand = interpolatedCommands[i];
for (const arg of typeMap[interpolatedCommand.type]) {
interpolatedCommand[arg] = (1 - t) * aCommand[arg] + t * bCommand[arg];
// do not use floats for flags (#27), round to integer
if (arg === 'largeArcFlag' || arg === 'sweepFlag') {
interpolatedCommand[arg] = Math.round(interpolatedCommand[arg]);
}
}
}
return interpolatedCommands;
};
}
/** @typedef InterpolateOptions */
/**
* 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
* @param {((command1, command2) => boolean|{
* excludeSegment?: (command1, command2) => boolean;
* snapEndsToInput?: boolean
* })} interpolateOptions The excludeSegment function or an options object
* - interpolateOptions.excludeSegment a function that takes a start command object and
* end command object and returns true if the segment should be excluded from splitting.
* - interpolateOptions.snapEndsToInput a boolean indicating whether end of input should
* be sourced from input argument or computed.
* @returns {Function} Interpolation function that maps t ([0, 1]) to a path `d` string.
*/
export default function interpolatePath(a, b, interpolateOptions) {
let aCommands = pathCommandsFromString(a);
let bCommands = pathCommandsFromString(b);
const { excludeSegment, snapEndsToInput } =
typeof interpolateOptions === 'object'
? interpolateOptions
: {
excludeSegment: interpolateOptions,
snapEndsToInput: true,
};
if (!aCommands.length && !bCommands.length) {
return function nullInterpolator() {
return '';
};
}
const commandInterpolator = interpolatePathCommands(aCommands, bCommands, {
excludeSegment,
snapEndsToInput,
});
return function pathStringInterpolator(t) {
// at 1 return the final value without the extensions used during interpolation
if (t === 1 && snapEndsToInput) {
return b == null ? '' : b;
}
const interpolatedCommands = commandInterpolator(t);
// convert to a string (fastest concat: https://jsperf.com/join-concat/150)
let interpolatedString = '';
for (const interpolatedCommand of interpolatedCommands) {
interpolatedString += commandToString(interpolatedCommand);
}
return interpolatedString;
};
}
================================================
FILE: src/split.js
================================================
/**
* de Casteljau's algorithm for drawing and splitting bezier curves.
* Inspired by https://pomax.github.io/bezierinfo/
*
* @param {Number[][]} points Array of [x,y] points: [start, control1, control2, ..., end]
* The original segment to split.
* @param {Number} t Where to split the curve (value between [0, 1])
* @return {Object} An object { left, right } where left is the segment from 0..t and
* right is the segment from t..1.
*/
function decasteljau(points, t) {
const left = [];
const right = [];
function decasteljauRecurse(points, t) {
if (points.length === 1) {
left.push(points[0]);
right.push(points[0]);
} else {
const newPoints = Array(points.length - 1);
for (let i = 0; i < newPoints.length; i++) {
if (i === 0) {
left.push(points[0]);
}
if (i === newPoints.length - 1) {
right.push(points[i + 1]);
}
newPoints[i] = [
((1 - t) * points[i][0]) + (t * points[i + 1][0]),
((1 - t) * points[i][1]) + (t * points[i + 1][1]),
];
}
decasteljauRecurse(newPoints, t);
}
}
if (points.length) {
decasteljauRecurse(points, t);
}
return { left, right: right.reverse() };
}
/**
* Convert segments represented as points back into a command object
*
* @param {Number[][]} points Array of [x,y] points: [start, control1, control2, ..., end]
* Represents a segment
* @return {Object} A command object representing the segment.
*/
function pointsToCommand(points) {
const command = {};
if (points.length === 4) {
command.x2 = points[2][0];
command.y2 = points[2][1];
}
if (points.length >= 3) {
command.x1 = points[1][0];
command.y1 = points[1][1];
}
command.x = points[points.length - 1][0];
command.y = points[points.length - 1][1];
if (points.length === 4) { // start, control1, control2, end
command.type = 'C';
} else if (points.length === 3) { // start, control, end
command.type = 'Q';
} else { // start, end
command.type = 'L';
}
return command;
}
/**
* Runs de Casteljau's algorithm enough times to produce the desired number of segments.
*
* @param {Number[][]} points Array of [x,y] points for de Casteljau (the initial segment to split)
* @param {Number} segmentCount Number of segments to split the original into
* @return {Number[][][]} Array of segments
*/
function splitCurveAsPoints(points, segmentCount) {
segmentCount = segmentCount || 2;
const segments = [];
let remainingCurve = points;
const tIncrement = 1 / segmentCount;
// x-----x-----x-----x
// t= 0.33 0.66 1
// x-----o-----------x
// r= 0.33
// x-----o-----x
// r= 0.5 (0.33 / (1 - 0.33)) === tIncrement / (1 - (tIncrement * (i - 1))
// x-----x-----x-----x----x
// t= 0.25 0.5 0.75 1
// x-----o----------------x
// r= 0.25
// x-----o----------x
// r= 0.33 (0.25 / (1 - 0.25))
// x-----o----x
// r= 0.5 (0.25 / (1 - 0.5))
for (let i = 0; i < segmentCount - 1; i++) {
const tRelative = tIncrement / (1 - (tIncrement * (i)));
const split = decasteljau(remainingCurve, tRelative);
segments.push(split.left);
remainingCurve = split.right;
}
// last segment is just to the end from the last point
segments.push(remainingCurve);
return segments;
}
/**
* Convert command objects to arrays of points, run de Casteljau's algorithm on it
* to split into to the desired number of segments.
*
* @param {Object} commandStart The start command object
* @param {Object} commandEnd The end command object
* @param {Number} segmentCount The number of segments to create
* @return {Object[]} An array of commands representing the segments in sequence
*/
export default function splitCurve(commandStart, commandEnd, segmentCount) {
const points = [[commandStart.x, commandStart.y]];
if (commandEnd.x1 != null) {
points.push([commandEnd.x1, commandEnd.y1]);
}
if (commandEnd.x2 != null) {
points.push([commandEnd.x2, commandEnd.y2]);
}
points.push([commandEnd.x, commandEnd.y]);
return splitCurveAsPoints(points, segmentCount).map(pointsToCommand);
}
================================================
FILE: test/interpolatePath-test.js
================================================
/* eslint-disable */
const tape = require('tape');
const interpolatePath = require('../').interpolatePath;
const APPROX_MAX_T = 0.999999999999;
const MIN_T = 0;
// helper to convert a path string to an array (e.g. 'M5,5 L10,10' => ['M', 5, 5, 'L', 10, 10]
function pathToItems(path) {
return path
.replace(/\s/g, '')
.split(/([A-Z,])/)
.filter((d) => d !== '' && d !== ',')
.map((d) => (isNaN(+d) ? d : +d));
}
// helper to ensure path1 and path2 are roughly equal
function approximatelyEqual(path1, path2) {
// convert to numbers and letters
const path1Items = pathToItems(path1);
const path2Items = pathToItems(path2);
const epsilon = 0.001;
if (path1Items.length !== path2Items.length) {
return false;
}
for (let i = 0; i < path1Items.length; i++) {
if (typeof path1Items[i] === 'string' && path1Items[i] !== path2Items[i]) {
return false;
}
// otherwise it's a number, check if approximately equal
if (Math.abs(path1Items[i] - path2Items[i]) > epsilon) {
return false;
}
}
return true;
}
tape(
'interpolatePath() interpolates line to line: len(A) = len(b)',
function (t) {
const a = 'M0,0L10,10L100,100';
const b = 'M10,10L20,20L200,200';
const interpolator = interpolatePath(a, b);
t.equal(interpolator(0), a);
t.equal(interpolator(1), b);
t.equal(interpolator(0.5), 'M5,5L15,15L150,150');
t.end();
}
);
tape(
'interpolatePath() interpolates line to line: len(A) > len(b)',
function (t) {
const a = 'M0,0L10,10L100,100';
const b = 'M10,10L20,20';
const interpolator = interpolatePath(a, b);
t.equal(interpolator(0), a);
// should not be extended anymore and should match exactly
t.equal(interpolator(1), b);
t.equal(
approximatelyEqual(interpolator(APPROX_MAX_T), 'M10,10L15,15L20,20'),
true
);
// should be half way between the last point of B and the last point of A
// here we get 12.5 since we split the 10,10-20,20 segment and end at L15,15
t.equal(interpolator(0.5), 'M5,5L12.5,12.5L60,60');
t.end();
}
);
tape(
'interpolatePath() interpolates line to line w/ snapEndsToInput: len(A) > len(b)',
function (t) {
const a = 'M0,0L10,10L100,100';
const b = 'M10,10L20,20';
const interpolator = interpolatePath(a, b, { snapEndsToInput: false });
t.equal(interpolator(0), a);
// should be extended
t.notEqual(interpolator(1), b);
t.equal(
approximatelyEqual(interpolator(APPROX_MAX_T), 'M10,10L15,15L20,20'),
true
);
// should be half way between the last point of B and the last point of A
// here we get 12.5 since we split the 10,10-20,20 segment and end at L15,15
t.equal(interpolator(0.5), 'M5,5L12.5,12.5L60,60');
t.end();
}
);
tape(
'interpolatePath() interpolates line to line: len(A) < len(b)',
function (t) {
const a = 'M0,0L10,10';
const b = 'M10,10L20,20L200,200';
const interpolator = interpolatePath(a, b);
// should be extended to match the length of b
t.equal(interpolator(0), 'M0,0L5,5L10,10');
t.equal(
approximatelyEqual(interpolator(APPROX_MAX_T), 'M10,10L20,20L200,200'),
true
);
// should be half way between the last point of B and the last point of A
t.equal(interpolator(0.5), 'M5,5L12.5,12.5L105,105');
t.end();
}
);
tape('interpolatePath() interpolates line to line: len(A)=1', function (t) {
const a = 'M0,0Z';
const b = 'M10,10L20,20L200,200';
const interpolator = interpolatePath(a, b);
// should be extended to match the length of b
t.equal(interpolator(0), 'M0,0L0,0L0,0');
t.equal(interpolator(1), b);
// should be half way between the last point of B and the last point of A
t.equal(interpolator(0.5), 'M5,5L10,10L100,100');
t.end();
});
tape('interpolatePath() interpolates line to line: len(B)=1', function (t) {
const a = 'M0,0L10,10L100,100';
const b = 'M10,10Z';
const interpolator = interpolatePath(a, b);
t.equal(interpolator(0), a);
// should not be extended anymore and should match exactly
t.equal(interpolator(1), b);
// should be half way between the last point of B and the last point of A
t.equal(interpolator(0.5), 'M5,5L10,10L55,55');
t.end();
});
tape('interpolatePath() interpolates line to line: A is null', function (t) {
const a = null;
const b = 'M10,10L20,20L200,200';
const interpolator = interpolatePath(a, b);
// should be extended to match the length of b
t.equal(interpolator(0), 'M10,10L10,10L10,10');
t.equal(interpolator(1), b);
// should be half way between the last point of B and the last point of A
t.equal(interpolator(0.5), 'M10,10L15,15L105,105');
t.end();
});
tape('interpolatePath() interpolates line to line: B is null', function (t) {
const a = 'M0,0L10,10L100,100';
const b = null;
const interpolator = interpolatePath(a, b);
t.equal(interpolator(0), a);
t.equal(interpolator(1), '');
// should be halfway towards the first point of a
t.equal(interpolator(0.5), 'M0,0L5,5L50,50');
t.end();
});
tape(
'interpolatePath() interpolates line to line: A is null and B is null',
function (t) {
const a = null;
const b = null;
const interpolator = interpolatePath(a, b);
t.equal(interpolator(0), '');
t.equal(interpolator(1), '');
// should be halfway towards the first point of a
t.equal(interpolator(0.5), '');
t.end();
}
);
tape(
'interpolatePath() interpolates where both A and B end in Z',
function (t) {
const a = 'M0,0Z';
const b = 'M10,10L20,20Z';
const interpolator = interpolatePath(a, b);
t.equal(interpolator(0), 'M0,0L0,0Z');
t.equal(interpolator(1), b);
// should be halfway towards the first point of a
t.equal(interpolator(0.5), 'M5,5L10,10Z');
t.end();
}
);
tape('interpolatePath() interpolates where A=null, B ends in Z', function (t) {
const a = null;
const b = 'M10,10L20,20Z';
const interpolator = interpolatePath(a, b);
t.equal(interpolator(0), 'M10,10L10,10Z');
t.equal(interpolator(1), b);
// should be halfway towards the first point of a
t.equal(interpolator(0.5), 'M10,10L15,15Z');
t.end();
});
tape(
'interpolatePath() interpolates A command using integer value',
function (t) {
const a = 'A10,10,0,0,1,20,20';
const b = 'A20,20,40,1,0,10,10';
const interpolator = interpolatePath(a, b);
// should be extended to match the length of b
t.equal(interpolator(0), a);
t.equal(interpolator(1), b);
// should be half way between the last point of B and the last point of A
t.equal(interpolator(0.5), 'A15,15,20,1,1,15,15');
t.equal(interpolator(0.75), 'A17.5,17.5,30,1,0,12.5,12.5');
t.equal(interpolator(0.25), 'A12.5,12.5,10,0,1,17.5,17.5');
t.end();
}
);
tape(
'interpolatePath() interpolates with other valid `d` characters',
function (t) {
const a =
'M0,0m0,0L0,0l0,0H0V0Q0,0,0,0q0,0,0,0C0,0,0,0,0,0c0,0,0,0,0,0T0,0t0,0' +
'S0,0,0,0s0,0,0,0A0,0,0,0,0,0,0';
const b =
'M4,4m4,4L4,4l4,4H4V4Q4,4,4,4q4,4,4,4C4,4,4,4,4,4c4,4,4,4,4,4T4,4t4,4' +
'S4,4,4,4s4,4,4,4A4,4,1,1,1,4,4';
const interpolator = interpolatePath(a, b);
t.equal(interpolator(0), a);
t.equal(interpolator(1), b);
// should be halfway towards the first point of a
t.equal(
interpolator(0.5),
'M2,2m2,2L2,2l2,2H2V2Q2,2,2,2q2,2,2,2C2,2,2,2,2,2c2,2,2,2,2,2' +
'T2,2t2,2S2,2,2,2s2,2,2,2A2,2,0.5,1,1,2,2'
);
t.end();
}
);
tape(
'interpolatePath() converts points in A to match types in B',
function (t) {
const a = 'M2,2 L3,3 C4,4,4,4,4,4 C5,5,5,5,5,5 L6,6 L7,7';
const b = 'M4,4 C5,5,5,5,5,5 L6,6 S7,7,7,7 H8 V9';
const interpolator = interpolatePath(a, b);
t.equal(interpolator(0), 'M2,2C3,3,3,3,3,3L4,4S5,5,5,5H6V7');
t.equal(interpolator(1), b);
// should be halfway towards the first point of a
t.equal(interpolator(0.5), 'M3,3C4,4,4,4,4,4L5,5S6,6,6,6H7V8');
t.end();
}
);
tape('interpolatePath() interpolates curves of different length', function (t) {
const a = 'M0,0C1,1,2,2,4,4C3,3,4,4,6,6';
const b = 'M2,2C5,5,6,6,4,4C6,6,7,7,5,5C8,8,9,9,6,6C10,10,11,11,7,7';
const interpolator = interpolatePath(a, b);
t.equal(
interpolator(0),
'M0,0C0.5,0.5,1,1,1.625,1.625C2.25,2.25,3,3,4,4C3.5,3.5,3.5,3.5,3.875,3.875C4.25,4.25,5,5,6,6'
);
t.equal(interpolator(1), b);
// should be halfway towards the first point of a
t.equal(
interpolator(0.5),
'M1,1C2.75,2.75,3.5,3.5,2.8125,2.8125C4.125,4.125,5,5,' +
'4.5,4.5C5.75,5.75,6.25,6.25,4.9375,4.9375C7.125,7.125,8,8,6.5,6.5'
);
t.end();
});
tape(
'interpolatePath() handles the case where path commands are followed by a space',
function (t) {
// IE bug fix.
const a = 'M 0 0 L 10 10 L 100 100';
const b = 'M10,10L20,20';
const interpolator = interpolatePath(a, b);
t.equal(interpolator(0), 'M0,0L10,10L100,100');
// should not be extended anymore and should match exactly
t.equal(interpolator(1), b);
t.equal(
approximatelyEqual(interpolator(APPROX_MAX_T), 'M10,10L15,15L20,20'),
true
);
// should be half way between the last point of B and the last point of A
// here we get 12.5 since we split the 10,10-20,20 segment and end at L15,15
t.equal(interpolator(0.5), 'M5,5L12.5,12.5L60,60');
t.end();
}
);
tape(
'interpolatePath() includes M when extending if it is the only item',
function (t) {
const a = 'M0,0';
const b = 'M10,10L20,20L30,30';
const interpolator = interpolatePath(a, b);
t.equal(interpolator(0), 'M0,0L0,0L0,0');
// should not be extended anymore and should match exactly
t.equal(interpolator(1), b);
// should be half way between the last point of B and the last point of A
t.equal(interpolator(0.5), 'M5,5L10,10L15,15');
t.end();
}
);
tape('interpolatePath() handles negative numbers properly', function (t) {
const a = 'M0,0L0,0';
const b = 'M-10,-10L20,20';
const interpolator = interpolatePath(a, b);
t.equal(interpolator(0), 'M0,0L0,0');
t.equal(interpolator(1), b);
// should be half way between the last point of B and the last point of A
t.equal(interpolator(0.5), 'M-5,-5L10,10');
t.end();
});
tape(
'interpolatePath() handles numbers in scientific notation properly',
function (t) {
const a = 'M0.000000e+0,0L0,0';
const b = 'M-1.000000e+1,-10L20,2.000000e+1';
const interpolator = interpolatePath(a, b);
t.equal(interpolator(0), 'M0,0L0,0');
t.equal(interpolator(1), b);
// should be half way between the last point of B and the last point of A
t.equal(interpolator(0.5), 'M-5,-5L10,10');
t.end();
}
);
tape('interpolatePath() handles leading spaces', function (t) {
const a = ' M0,0L10,10L100,100';
const b = `
\tM10,10L20,20L200,200`;
const interpolator = interpolatePath(a, b);
t.equal(interpolator(0), 'M0,0L10,10L100,100');
t.equal(interpolator(1), b);
t.equal(interpolator(0.5), 'M5,5L15,15L150,150');
t.end();
});
tape('interpolatePath() speed check', function (t) {
const a =
'M-0.04470287711750471,0C-0.04470287711750471,0,0.0549156720075664,183.81543114379014,0.4023258940575424,183.81833500000002C0.526136890736335,183.81936988414768,0.6370732629693643,166.28385175799752,0.8493546652325895,160.47603C0.9758852381605138,157.01427118991134,1.159198737306147,152.19239531779905,1.2963834364076363,152.19358100000002C1.462792685448858,152.1950192688883,1.570804424917851,160.8290137029863,1.7434122075826834,164.390285C1.8780614267538214,167.1683891619115,2.0332188018527293,171.80289993588647,2.1904409787577306,171.803948C2.332724496768761,171.80489648101016,2.238234072817103,165.9019150280637,2.6374697499327775,165.737611C2.7620960100887855,165.68632150429636,2.9586685404548496,166.1799550006193,3.0844985211078244,166.126846C3.5479378120008116,165.93124239695328,3.3104342111345733,160.0258386390105,3.5315272922828718,158.098832C3.6572342833339717,157.00319298195663,3.8332512551491544,155.5370455129371,3.9785560634579187,155.538306C4.131679880260214,155.53963431523158,4.2821873821017515,158.39169317429364,4.425584834632966,158.39C4.5812039682111845,158.38816251745112,4.746590024452187,155.014470835385,4.872613605808013,155.01999999999998C5.090049217183896,155.02953978037698,5.1522628706299,165.12815540046833,5.31964237698306,165.13C5.456390430448821,165.13150702676137,5.6299230946923435,160.86445592906185,5.766671148158107,158.39C5.9340506545112675,155.3612679529824,6.046320412979994,151.30873204701757,6.213699919333155,148.27999999999997C6.350447972798918,145.80554407093814,6.511719100116519,141.54000000000002,6.6607286905082015,141.54000000000002C6.809738280899883,141.54000000000002,6.971009408217484,148.28150702676132,7.1077574616832475,148.27999999999997C7.275136968036411,148.27815540046836,7.337350621482413,138.17953978037704,7.554786232858295,138.17000000000002C7.68080981421412,138.164470835385,7.870774944195174,140.22140955710998,8.001815004033343,141.54000000000002C8.186529853242384,143.3986929457122,7.95764144262283,147.8202973291211,8.448843775208388,148.27999999999997C8.575207274844532,148.39826010267208,8.769509046747293,148.16173989732786,8.895872546383437,148.27999999999997C9.387074878969003,148.73970267087893,9.1938917271668,155.01999999999998,9.342901317558484,155.01999999999998C9.491910907950167,155.01999999999998,9.605215239524492,148.28606561227687,9.78993008873353,148.27999999999997C9.920970148571701,148.27569694478208,10.087949269516896,150.52666666666664,10.236958859908578,151.64999999999998C10.385968450300261,152.7733333333333,10.534978040691943,155.01999999999998,10.683987631083626,155.01999999999998C10.832997221475308,155.02,10.98200681186699,152.7733333333333,11.131016402258672,151.64999999999998C11.280025992650353,150.52666666666664,11.453836251401587,148.2738634816297,11.578045173433718,148.27999999999997C11.8254500696269,148.29222299224205,11.777669048415582,161.74777700775797,12.025073944608765,161.76C12.149282866640899,161.7661365183703,12.32309312539213,159.51333333333335,12.472102715783812,158.39C12.621112306175496,157.26666666666662,12.57791324533422,155.31894474475482,12.919131486958861,155.01999999999998C13.042866096536695,154.9115948599305,13.237590505198265,155.14301047090632,13.366160258133908,155.01999999999998C13.97788875782087,154.4347222881343,13.645809522955792,147.9387320470176,13.813189029308955,144.91000000000003C13.949937082774717,142.43554407093816,14.13240491590533,138.167462320762,14.260217800484,138.17000000000002C14.462121588496245,138.17400872770077,14.432092534979779,155.00549202791083,14.707246571659047,155.01999999999998C14.830815229907705,155.02651537105035,15.023235282995927,151.6456969447821,15.154275342834095,151.64999999999998C15.33899019204314,151.65606561227688,15.46455606054338,158.39150702676136,15.601304114009142,158.39C15.768683620362305,158.38815540046832,15.908576657164227,151.892368483562,16.04833288518419,148.27999999999997C16.209675024633082,144.10968667748853,16.247956760166055,137.79149787222804,16.495361656359236,134.8C16.619570578391365,133.29812703108053,16.793380837142603,132.55333333333334,16.942390427534285,131.43C17.091400017925967,130.30666666666667,17.24040960831765,129.18333333333337,17.38941919870933,128.06C17.53842878910101,126.93666666666664,17.687438379492693,125.81333333333333,17.836447969884375,124.69C17.985457560276057,123.56666666666666,18.134467150667742,121.31999999999998,18.283476741059424,121.32C18.432486331451106,121.31999999999998,18.599465452396306,124.6943030552179,18.730505512234473,124.69C18.915220361443517,124.68393438772318,19.028524693017836,117.94999999999997,19.177534283409518,117.94999999999999C19.3265438738012,117.94999999999997,19.439848205375533,124.68393438772318,19.624563054584566,124.69C19.75560311442274,124.6943030552179,19.940551765921448,121.3156969447821,20.071591825759615,121.32C20.256306674968656,121.32606561227685,20.333905747725627,126.20130705428781,20.51862059693466,128.06C20.64966065677283,129.37859044289004,20.84144044607757,129.92812703108058,20.965649368109705,131.43C21.213054264302883,134.421497872228,21.16527324309157,144.89777700775795,21.412678139284754,144.91000000000003C21.536887061316886,144.91613651837034,21.51848866883516,141.83894474475485,21.8597069104598,141.54000000000002C21.98344152003763,141.43159485993053,22.178165928699205,141.4169895290937,22.306735681634848,141.54000000000002C22.918464181321802,142.12527771186566,22.142035953122942,151.0647222881343,22.753764452809897,151.64999999999998C22.882334205745536,151.77301047090629,23.070555340952627,151.77599048035455,23.20079322398494,151.64999999999998C23.9161684136525,150.95795509200977,22.93244680549243,138.8620449079902,23.64782199515999,138.17000000000002C23.778059878192316,138.0440095196455,23.966281013399392,138.04698952909368,24.09485076633504,138.17000000000002C24.706579266021986,138.75527771186572,24.392869947118406,144.91,24.541879537510084,148.27999999999997C24.690889127901762,151.64999999999995,24.771472697309246,155.92646887031472,24.988908308685133,158.39C25.11493189004096,159.81783886126087,25.286927489468493,161.76,25.435937079860178,161.76C25.58494667025186,161.76,25.7569422696794,158.38447083538503,25.882965851035223,158.39C26.100401462411106,158.39953978037698,26.112559010834385,166.03646887031465,26.32999462221027,168.5C26.456018203566096,169.92783886126094,26.62801380299364,171.87,26.777023393385317,171.87C26.926032983777,171.86999999999998,27.093012104722195,168.4956969447821,27.224052164560366,168.5C27.408767013769406,168.50606561227687,27.54326805115674,172.48594534810564,27.67108093573541,175.24C27.872984723747663,179.59053217399182,27.8429556702312,192.07549202791083,28.11810970691046,192.09C28.24167836515911,192.09651537105034,28.434098418247338,190.03859044289004,28.565138478085508,188.72C28.749853327294552,186.86130705428783,28.88120833356565,184.61340296492668,29.012167249260553,181.98C29.197218847796908,178.25886787498004,29.211791124242424,168.51222299224204,29.459196020435602,168.5C29.583404942467737,168.49386348162972,29.75721520121897,171.87,29.906224791610647,171.87C30.055234382002325,171.86999999999998,30.222213502947525,168.4956969447821,30.353253562785696,168.5C30.537968411994743,168.50606561227687,30.615567484751708,175.2339343877232,30.800282333960745,175.24C30.93132239379891,175.24430305521793,30.90609286351115,172.16894474475487,31.24731110513579,171.87C31.37104571471362,171.7615948599305,31.570605266733008,171.97840514006953,31.69433987631084,171.87C32.03555811793548,171.57105525524514,32.01534506613006,169.92783886126094,32.14136864748588,168.5C32.35880425886176,166.0364688703147,32.42101791230777,158.39184459953165,32.588397418660925,158.39C32.72514547212669,158.3884929732386,32.886416599444296,165.13,33.035426189835974,165.13C33.18443578022765,165.13,33.34570690754526,158.3884929732386,33.48245496101102,158.39C33.64983446736419,158.39184459953162,33.71204812081018,166.03646887031465,33.92948373218607,168.5C34.0555073135419,169.92783886126094,34.227502912969435,170.74666666666667,34.37651250336112,171.87C34.525522093752805,172.99333333333334,34.700095795964636,175.24678242135772,34.82354127453617,175.24C35.12464077310837,175.22345679652634,34.969470547139004,155.0365432034736,35.27057004571121,155.01999999999998C35.39401552428274,155.0132175786423,35.56858922649458,158.39,35.71759881688626,158.39C35.86660840727794,158.39,36.03358752822314,156.33859044289002,36.16462758806131,155.01999999999998C36.34934243727036,153.1613070542878,36.426941510027326,148.28606561227687,36.611656359236356,148.27999999999997C36.74269641907453,148.27569694478208,36.927645070573234,151.65430305521787,37.058685130411405,151.64999999999998C37.24339997962044,151.64393438772316,37.36896584812068,144.90849297323865,37.50571390158645,144.91000000000003C37.67309340793961,144.91184459953172,37.803733082369824,151.65,37.952742672761495,155.01999999999998C38.10175226315317,158.39,38.23239193758339,165.12815540046833,38.399771443936544,165.13C38.5365194974023,165.13150702676137,38.35559788252603,158.84970267087888,38.846800215111585,158.39C38.97316371474772,158.2717398973279,39.16525923335099,158.26698952909368,39.293828986286634,158.39C39.90555748597361,158.9752777118657,39.129129257774736,167.91472228813439,39.74085775746168,168.5C39.86942751039733,168.62301047090634,40.05931677570109,168.37698952909366,40.18788652863673,168.5C40.79961502832371,169.08527771186576,40.41747968843589,176.1464688703147,40.63491529981177,178.61C40.7609388811676,180.0378388612609,40.950904011148644,180.66140955710995,41.08194407098682,181.98C41.26665892019586,183.8386929457122,41.37996325177019,186.47333333333336,41.52897284216187,188.72C41.67798243255355,190.9666666666667,41.79128676412788,195.4539343877232,41.97600161333691,195.46C42.107041673175075,195.46430305521787,42.299461726263296,193.64691470941213,42.42303038451196,192.09C42.69818442119122,188.6231713305648,42.59490511900774,175.25450797208913,42.87005915568701,175.24C42.99362781393566,175.23348462894964,43.16807833647037,177.48666666666668,43.31708792686206,178.61C43.46609751725374,179.73333333333335,43.63809311668128,181.985529164615,43.76411669803711,181.98C43.981552309413004,181.97046021962302,44.043765962858984,174.8987320470176,44.21114546921215,171.87C44.34789352267792,169.39554407093817,44.50916464999551,167.37666666666667,44.6581742403872,165.13C44.80718383077888,162.88333333333335,44.92048816235322,158.39606561227686,45.105203011562246,158.39C45.236243071400416,158.3856969447821,45.42620820138147,161.765529164615,45.552231782737294,161.76C45.76966739411318,161.750460219623,45.781824942536474,154.1135311296853,45.99926055391234,151.64999999999998C46.125284135268174,150.2221611387391,46.2972797346957,149.40333333333328,46.446289325087385,148.27999999999997C46.59529891547908,147.1566666666667,46.744308505870755,144.91000000000003,46.89331809626243,144.91000000000003C47.04232768665411,144.91000000000003,47.20930680759931,146.96140955710993,47.34034686743748,148.27999999999997C47.52506171664652,150.1386929457122,47.65062758514677,155.02150702676133,47.78737563861253,155.01999999999998C47.95475514496568,155.0181554004683,48.08539481939589,144.91,48.23440440978758,144.91000000000003C48.38341400017925,144.91,48.51405367460946,151.99126795298238,48.68143318096262,155.01999999999998C48.81818123442838,157.49445592906184,48.979452361745984,159.51333333333332,49.12846195213767,161.76C49.277471542529355,164.0066666666667,49.084288390727174,168.04029732912116,49.57549072331272,168.5C49.70185422294885,168.61826010267208,49.89394974155212,168.37698952909366,50.02251949448777,168.5C50.63424799417474,169.08527771186576,50.252112654286925,178.60046021962302,50.469548265662816,178.61C50.595571847018654,178.61552916461503,50.767567446446186,176.36333333333334,50.916577036837865,175.24C51.06558662722956,174.1166666666667,51.232565748174736,171.86569694478212,51.363605808012906,171.87C51.54832065722195,171.87606561227685,51.67388652572219,178.6115070267614,51.810634579187955,178.61C51.978014085541126,178.60815540046838,51.64593485067605,169.08527771186573,52.257663350363,168.5C52.38623310329865,168.3769895290937,52.555682531146374,168.5,52.70469212153805,168.5C52.85370171192974,168.5,53.00271130232142,168.50000000000003,53.1517208927131,168.5C53.30073048310478,168.5,53.47017991095251,168.62301047090634,53.59874966388814,168.5C54.2104781635751,167.91472228813433,53.87839892871004,161.41873204701758,54.04577843506319,158.39C54.18252648852896,155.91554407093813,54.36184829054334,154.2834029649267,54.49280720623824,151.64999999999998C54.67785880477459,147.92886787498006,54.692431081220114,141.16149787222804,54.93983597741329,138.17000000000002C55.06404489944543,136.6681270310806,55.04564650696367,135.09894474475487,55.38686474858832,134.8C55.510599358166154,134.69159485993052,55.705323766827725,134.92301047090635,55.83389351976337,134.8C56.44562201945034,134.21472228813437,56.06348667956252,127.15353112968529,56.28092229093842,124.69C56.40694587229425,123.26216113873913,56.603742140081344,121.3138634816297,56.72795106211347,121.32C56.97535595830665,121.33222299224202,56.92757493709535,134.78777700775794,57.17497983328852,134.8C57.29918875532064,134.80613651837032,57.49096854462539,132.74859044289002,57.62200860446356,131.43C57.8067234536726,129.5713070542878,57.88432252642958,126.54869294571226,58.06903737563861,124.69C58.20007743547678,123.37140955710998,58.38502608697548,121.3156969447821,58.51606614681366,121.32C58.700780996022715,121.32606561227685,58.471892585403154,127.60029732912115,58.963094917988705,128.06C59.08945841762485,128.17826010267208,59.281553936228114,127.93698952909368,59.410123689163754,128.06C60.02185218885072,128.64527771186573,59.70814286994711,134.8,59.8571524603388,138.17000000000002C60.00616205073049,141.54000000000002,60.15517164112215,148.27999999999994,60.304181231513844,148.27999999999997C60.453190821905515,148.27999999999994,60.533774391312996,138.17953978037704,60.75121000268889,138.17000000000002C60.87723358404471,138.164470835385,61.074670115615305,141.54651537105036,61.19823877386394,141.54000000000002C61.47339281054321,141.5254920279109,61.471860877574294,124.69153157078384,61.64526754503899,124.69C61.779629740338954,124.68881327969805,61.943286725822375,131.43,62.09229631621404,134.8C62.24130590660573,138.17000000000004,62.3218894760132,142.4464688703147,62.53932508738908,144.91000000000003C62.665348668744905,146.3378388612609,62.837344268172465,147.15666666666667,62.98635385856413,148.27999999999997C63.135363448955815,149.4033333333333,63.2843730393475,150.52666666666664,63.43338262973918,151.64999999999998C63.58239222013086,152.7733333333333,63.73140181052255,153.89666666666665,63.88041140091423,155.01999999999998C64.02942099130591,156.14333333333337,64.20323125005716,158.3961365183703,64.32744017208927,158.39C64.57484506828246,158.37777700775794,64.63288085855255,144.9095308782152,64.77446894326432,144.91000000000003C64.93275358119851,144.91052444223686,64.41376759838283,160.97341467650082,65.22149771443937,161.76C65.35303766366552,161.88809649096692,65.53995673267877,161.63698952909365,65.66852648561442,161.76C66.28025498530138,162.34527771186572,65.94817575043629,171.86815540046834,66.11555525678946,171.87C66.25230331025523,171.87150702676138,66.37786917875546,166.98869294571224,66.5625840279645,165.13C66.69362408780268,163.81140955710993,66.86060320874789,161.76,67.00961279913956,161.76C67.15862238953125,161.76000000000002,67.30763197992293,165.13000000000002,67.4566415703146,165.13C67.60565116070629,165.13,67.7726302816515,163.07859044289003,67.90367034148966,161.76C68.0883851906987,159.90130705428774,68.21395105919892,157.49445592906184,68.35069911266469,155.01999999999998C68.51807861901784,151.9912679529824,68.58029227246386,144.91953978037702,68.79772788383974,144.91000000000003C68.92375146519556,144.90447083538504,69.09574706462311,148.27999999999997,69.24475665501478,148.27999999999997C69.39376624540648,148.28000000000003,69.565761844834,144.904470835385,69.69178542618984,144.91000000000003C69.90922103756573,144.919539780377,69.921378585989,155.010460219623,70.13881419736488,155.01999999999998C70.2648377787207,155.02552916461497,70.43683337814824,151.64999999999998,70.58584296853992,151.64999999999998C70.7348525589316,151.64999999999998,70.88386214932329,155.01999999999998,71.03287173971498,155.01999999999998C71.18188133010666,155.01999999999998,71.3538769295342,153.07783886126086,71.47990051089002,151.64999999999998C71.69733612226592,149.1864688703147,71.70949367068921,144.00353112968534,71.92692928206507,141.54000000000002C72.0529528634209,140.1121611387391,72.22494846284845,139.29333333333338,72.37395805324012,138.17000000000002C72.52296764363182,137.0466666666667,72.69496324305933,134.79447083538506,72.82098682441516,134.8C73.03842243579106,134.80953978037704,72.65628709590325,144.32472228813435,73.26801559559021,144.91000000000003C73.39658534852587,145.03301047090636,73.59130975718743,145.01840514006955,73.71504436676526,144.91000000000003C74.05626260838993,144.61105525524522,74.03604955658449,142.96783886126093,74.16207313794031,141.54000000000002C74.3795087493162,139.07646887031476,74.39166629773949,131.43953978037703,74.60910190911535,131.43C74.73512549047119,131.42447083538505,74.92509062045222,134.8043030552179,75.0561306802904,134.8C75.24084552949944,134.7939343877232,75.3184446022564,129.91869294571222,75.50315945146545,128.06C75.63419951130362,126.74140955710996,75.82416464128467,124.68447083538503,75.95018822264049,124.69C76.1676238340164,124.699539780377,76.22983748746239,131.77126795298244,76.39721699381555,134.8C76.53396504728131,137.27445592906187,76.70749771152482,139.06554407093816,76.84424576499059,141.54000000000002C77.01162527134375,144.56873204701756,77.07383892478978,151.640460219623,77.29127453616564,151.64999999999998C77.41729811752147,151.65552916461496,77.39708506571606,148.57894474475484,77.73830330734069,148.27999999999997C77.86203791691852,148.17159485993048,78.0567623255801,148.1569895290937,78.18533207851573,148.27999999999997C78.7970605782027,148.86527771186564,78.48335125929913,155.02,78.63236084969078,158.39C78.78137044008248,161.76,78.46766112117886,167.91472228813436,79.07938962086583,168.5C79.20795937380147,168.62301047090637,79.40268378246307,168.60840514006952,79.52641839204088,168.5C79.86763663366553,168.20105525524508,79.84240710337775,165.1256969447821,79.97344716321592,165.13C80.15816201242497,165.13606561227684,80.23576108518192,171.86393438772316,80.42047593439096,171.87C80.55151599422913,171.8743030552179,80.5262864639414,168.79894474475492,80.86750470556602,168.5C80.99123931514386,168.3915948599305,81.1655238863494,168.5,81.31453347674106,168.5C81.46354306713275,168.50000000000003,81.63782763833828,168.60840514006952,81.7615622479161,168.5C82.10278048954073,168.20105525524517,82.07755095925297,165.1256969447821,82.20859101909114,165.13C82.39330586830017,165.13606561227684,82.50661019987453,171.87,82.6556197902662,171.87C82.80462938065789,171.87,82.6114462288557,165.58970267087886,83.10264856144124,165.13C83.22901206107738,165.01173989732789,83.40066774222461,165.13,83.5496773326163,165.13C83.69868692300798,165.13,83.87297149421352,165.02159485993053,83.99670610379134,165.13C84.337924345416,165.42894474475486,84.29472528457472,167.37666666666667,84.4437348749664,168.5C84.5927444653581,169.62333333333336,84.75972358630327,171.8743030552179,84.89076364614144,171.87C85.07547849535048,171.86393438772316,85.15307756810748,166.9886929457122,85.33779241731649,165.13C85.46883247715465,163.81140955710995,85.6606122664594,161.75386348162968,85.78482118849153,161.76C86.03222608468471,161.77222299224204,86.04679836113023,175.23693919668935,86.23184995966659,175.24C86.36280887536147,175.24216609575862,86.52986914044993,170.74666666666667,86.67887873084162,168.5C86.82788832123332,166.25333333333336,86.94119265280763,161.76606561227683,87.12590750201667,161.76C87.25694756185484,161.75569694478207,87.42392668280006,165.13000000000002,87.57293627319171,165.13C87.72194586358341,165.13000000000002,87.88892498452859,163.07859044289,88.01996504436676,161.76C88.2046798935758,159.90130705428777,88.2822789663328,156.8786929457122,88.46699381554181,155.01999999999998C88.59803387537997,153.70140955710994,88.57280434509221,151.9489447447548,88.91402258671685,151.64999999999998C89.0377571962947,151.5415948599305,89.21204176750022,151.64999999999998,89.36105135789191,151.64999999999998C89.5100609482836,151.64999999999998,89.6795103761313,151.77301047090629,89.80808012906695,151.64999999999998C90.4198086287539,151.06472228813428,90.08772939388888,144.56873204701762,90.255108900242,141.54000000000002C90.39185695370776,139.06554407093816,90.51742282220799,134.80606561227685,90.70213767141703,134.8C90.83317773125522,134.79569694478212,90.80794820096749,137.8710552552452,91.14916644259209,138.17000000000002C91.27290105216991,138.2784051400695,91.47246060418932,138.2784051400695,91.59619521376713,138.17000000000002C91.9374134553918,137.87105525524518,91.91218392510402,136.11859044289002,92.04322398494219,134.8C92.22793883415122,132.94130705428776,92.34124316572556,128.06,92.49025275611723,128.06C92.63926234650891,128.06,92.44607919470673,134.34029732912114,92.93728152729228,134.8C93.06364502692843,134.9182601026721,93.2557405455317,134.92301047090638,93.38431029846733,134.8C93.99603879815427,134.21472228813434,93.21961056995545,125.27527771186564,93.83133906964238,124.69C93.95990882257804,124.56698952909369,94.14979808788179,124.56698952909369,94.27836784081742,124.69C94.89009634050436,125.27527771186566,94.58564038397252,134.80077128684863,94.72539661199248,134.8C94.88673875144138,134.799109584796,95.01108324371863,121.320890415204,95.17242538316751,121.32C95.3121816111875,121.31922871315139,95.40201854296669,128.9664688703147,95.61945415434258,131.43C95.74547773569843,132.85783886126092,95.91747333512593,133.67666666666668,96.0664829255176,134.8C96.21549251590928,135.92333333333335,96.38247163685449,136.85140955710997,96.51351169669266,138.17000000000002C96.69822654590172,140.02869294571227,96.77582561865863,143.05130705428778,96.9605404678677,144.91000000000003C97.09158052770584,146.22859044288998,97.25855964865106,147.15666666666667,97.40756923904276,148.27999999999997C97.55657882943446,149.4033333333333,97.72857442886198,150.2221611387391,97.8545980102178,151.64999999999998C98.07203362159369,154.1135311296853,98.1342472750397,158.73126795298242,98.30162678139286,161.76C98.4383748348586,164.23445592906185,98.61769663687299,168.50216609575867,98.7486555525679,168.5C98.93370715110424,168.4969391966893,99.03434218429403,155.02089041520395,99.19568432374295,155.01999999999998C99.3354405517629,155.01922871315136,99.50835089961801,165.1311867203019,99.642713094918,165.13C99.81611976238271,165.1284684292162,99.91633519862836,148.2815315707838,100.08974186609305,148.27999999999997C100.22410406139302,148.27881327969806,100.3693911309149,158.38815540046832,100.53677063726808,158.39C100.67351869073384,158.39150702676133,100.49259707585756,152.10970267087885,100.98379940844312,151.64999999999998C101.11016290807926,151.53173989732787,101.30059029658585,151.77599048035452,101.43082817961817,151.64999999999998C102.14620336928574,150.9579550920098,101.16248176112563,138.86204490799025,101.87785695079322,138.17000000000002C102.00809483382555,138.04400951964547,102.19852222233212,138.28826010267207,102.32488572196827,138.17000000000002C102.81608805455383,137.71029732912112,102.58719964393428,131.43606561227688,102.77191449314331,131.43C102.90295455298148,131.42569694478212,103.09473434228622,133.29812703108053,103.21894326431837,134.8C103.46634816051156,137.79149787222806,102.95059684582583,147.58795509200976,103.66597203549341,148.27999999999997C103.79620991852575,148.40599048035455,103.98663730703234,148.1617398973279,104.11300080666847,148.27999999999997C104.60420313925401,148.73970267087884,104.42328152437773,155.02150702676133,104.5600295778435,155.01999999999998C104.72740908419667,155.01815540046832,104.78962273764269,147.37353112968535,105.00705834901855,144.91000000000003C105.13308193037437,143.48216113873914,105.32806353883777,142.96783886126093,105.45408712019359,141.54000000000002C105.6715227315695,139.07646887031476,105.73373638501548,131.43184459953167,105.90111589136865,131.43C106.03786394483441,131.42849297323863,105.85694232995813,137.7102973291211,106.34814466254369,138.17000000000002C106.47450816217983,138.28826010267207,106.67143882414095,138.27840514006954,106.79517343371874,138.17000000000002C107.13639167534339,137.87105525524515,107.09319261450212,134.8,107.24220220489379,134.8C107.39121179528549,134.80000000000004,107.54022138567717,137.04666666666668,107.68923097606884,138.17000000000002C107.83824056646053,139.29333333333335,107.9872501568522,140.41666666666669,108.13625974724388,141.54000000000002C108.28526933763558,142.66333333333338,108.24207027679432,144.61105525524522,108.58328851841894,144.91000000000003C108.70702312799678,145.01840514006952,108.90658268001613,144.8015948599305,109.03031728959397,144.91000000000003C109.37153553121856,145.20894474475486,109.35132247941323,146.85216113873912,109.47734606076904,148.27999999999997C109.69478167214494,150.7435311296853,109.7753652415524,155.01999999999998,109.92437483194406,158.39C110.07338442233575,161.75999999999996,109.75967510343216,167.91472228813433,110.37140360311912,168.5C110.49997335605477,168.62301047090634,110.69469776471632,168.6084051400695,110.81843237429416,168.5C111.15965061591882,168.20105525524522,110.92424290384461,165.4289447447549,111.26546114546922,165.13C111.38919575504707,165.0215948599305,111.58392016370863,165.25301047090633,111.71248991664426,165.13C112.3242184163312,164.54472228813427,112.01050909742762,158.39,112.15951868781931,155.01999999999998C112.308528278211,151.65,112.45753786860267,148.28000000000003,112.60654745899436,144.91000000000003C112.75555704938603,141.54000000000002,112.44184773048246,135.38527771186568,113.05357623016941,134.8C113.18214598310506,134.67698952909373,113.37424150170834,134.68173989732796,113.50060500134445,134.8C113.99180733393001,135.2597026708789,113.76291892331044,141.5339343877232,113.94763377251948,141.54000000000002C114.07867383235765,141.5443030552179,114.26863896233873,139.5978388612609,114.39466254369454,138.17000000000002C114.61209815507044,135.70646887031475,114.6743118085164,128.06184459953164,114.84169131486958,128.06C114.97843936833534,128.05849297323866,115.13971049565295,134.8,115.28872008604463,134.8C115.4377296764363,134.8,115.59900080375392,128.05849297323866,115.73574885721968,128.06C115.90312836357282,128.06184459953164,116.01539812204156,135.1412679529824,116.18277762839473,138.17000000000002C116.3195256818605,140.64445592906188,116.48079680917809,142.6633333333334,116.62980639956977,144.91000000000003C116.77881598996147,147.1566666666667,116.89212032153578,151.64393438772316,117.07683517074483,151.64999999999998C117.20787523058301,151.65430305521792,117.18264570029521,148.5789447447548,117.52386394191987,148.27999999999997C117.64759855149774,148.1715948599305,117.84715810351707,148.38840514006947,117.97089271309493,148.27999999999997C118.31211095471956,147.98105525524514,118.29371256223781,146.4118729689194,118.41792148426995,144.91000000000003C118.66532638046311,141.91850212777206,118.71594066505332,131.43,118.86495025544501,131.43C119.0139598458367,131.43,118.5966038369525,144.21795509200982,119.31197902662005,144.91000000000003C119.44221690965237,145.03599048035457,119.63043804485945,144.78698952909372,119.7590077977951,144.91000000000003C120.37073629748207,145.49527771186575,120.03865706261696,155.0181554004683,120.20603656897015,155.01999999999998C120.34278462243591,155.02150702676133,120.51631728667945,150.75445592906186,120.6530653401452,148.27999999999997C120.82044484649833,145.25126795298237,120.88265849994438,140.6335311296853,121.10009411132025,138.17000000000002C121.22611769267604,136.7421611387391,121.42109930113948,134.79447083538503,121.5471228824953,134.8C121.7645584938712,134.80953978037704,121.84514206327864,141.54000000000002,121.99415165367034,144.91000000000003C122.14316124406203,148.28000000000003,121.82945192515845,154.4347222881343,122.4411804248454,155.01999999999998C122.56975017778105,155.1430104709063,122.75561838073719,154.89030813576704,122.88820919602043,155.01999999999998C123.7800540976,155.89234570241834,123.11725689853282,175.23519021952075,123.3352379671955,175.24C123.46121218192263,175.24277963734468,123.5975518891615,170.35869294571225,123.78226673837052,168.5C123.91330679820871,167.18140955711,124.09825544970742,165.12569694478208,124.22929550954558,165.13C124.41401035875462,165.1360656122768,124.18512194813503,171.41029732912114,124.67632428072062,171.87C124.80268778035675,171.9882601026721,124.99698955225954,171.7517398973279,125.12335305189568,171.87C125.61455538448125,172.32970267087893,125.38566697386169,176.75130705428776,125.57038182307072,178.61C125.70142188290887,179.92859044289003,125.86840100385409,180.85666666666668,126.01741059424577,181.98C126.16642018463747,183.10333333333335,126.33841578406499,185.35552916461495,126.46443936542082,185.35C126.6818749767967,185.34046021962303,126.69403252521997,175.24953978037698,126.91146813659587,175.24C127.03749171795171,175.23447083538503,127.20948731737921,178.60999999999999,127.35849690777091,178.61C127.50750649816258,178.61,127.67448561910776,176.55859044289005,127.80552567894594,175.24C127.99024052815497,173.3813070542878,128.06783960091198,168.50606561227687,128.252554450121,168.5C128.38359450995918,168.4956969447821,128.5505736309044,170.7466666666667,128.69958322129605,171.87C128.84859281168775,172.99333333333337,128.8053937508465,174.94105525524517,129.1466119924711,175.24C129.27034660204893,175.34840514006953,129.46990615406833,175.34840514006953,129.59364076364614,175.24C129.9348590052708,174.94105525524517,129.69945129319655,172.16894474475487,130.04066953482118,171.87C130.16440414439901,171.76159485993054,130.36133480636008,171.75173989732795,130.48769830599622,171.87C130.97890063858176,172.32970267087887,130.7500122279623,178.6039343877232,130.9347270771713,178.61C131.0657671370095,178.6143030552179,131.25573226699052,176.66783886126095,131.38175584834633,175.24C131.59919145972222,172.77646887031474,131.6113490081455,167.59353112968535,131.82878461952137,165.13C131.9548082008772,163.70216113873911,132.12680380030474,161.76000000000002,132.2758133906964,161.76C132.4248229810881,161.76000000000002,132.59180210203328,163.81140955710995,132.72284216187148,165.13C132.90755701108054,166.98869294571224,132.9851560838375,171.86393438772316,133.16987093304652,171.87C133.3009109928847,171.8743030552179,133.27568146259694,168.79894474475486,133.61689970422157,168.5C133.7406343137994,168.39159485993054,133.935358722461,168.37698952909372,134.0639284753966,168.5C134.6756569750836,169.0852777118657,134.29352163519582,178.60046021962307,134.51095724657168,178.61C134.63698082792752,178.615529164615,134.82694595790852,176.55859044289008,134.9579860177467,175.24C135.1427008669557,173.38130705428776,135.25600519853006,170.74666666666667,135.40501478892176,168.5C135.55402437931346,166.25333333333336,135.7030339697051,161.76,135.8520435600968,161.76C136.00105315048847,161.76,135.80786999868633,168.04029732912116,136.29907233127184,168.5C136.425435830908,168.61826010267208,136.59709151205521,168.5,136.7461011024469,168.5C136.89511069283856,168.5,137.06456012068634,168.6230104709063,137.19312987362196,168.5C137.80485837330892,167.91472228813427,137.42272303342114,160.85353112968534,137.640158644797,158.39C137.7661822261528,156.96216113873913,137.95614735613384,155.0156969447821,138.08718741597204,155.01999999999998C138.2719022651811,155.02606561227682,138.34950133793805,161.75393438772318,138.53421618714708,161.76C138.66525624698525,161.76430305521788,138.64002671669758,158.6889447447549,138.98124495832215,158.39C139.10497956789996,158.2815948599305,139.30453911991933,158.49840514006948,139.42827372949716,158.39C139.76949197112185,158.09105525524518,139.7510935786401,155.0138634816297,139.87530250067223,155.01999999999998C140.12270739686542,155.03222299224205,140.1372796733109,168.4969391966893,140.32233127184728,168.5C140.45329018754217,168.50216609575864,140.27815771043677,162.2197026708789,140.76936004302232,161.76C140.89572354265843,161.6417398973279,141.0878190612617,161.63698952909368,141.21638881419736,161.76C141.82811731388432,162.34527771186566,141.44598197399648,171.86046021962298,141.6634175853724,171.87C141.78944116672824,171.875529164615,141.9794062967093,169.81859044289,142.11044635654747,168.5C142.29516120575653,166.6413070542878,142.066272795137,162.2197026708789,142.5574751277225,161.76C142.68383862735863,161.6417398973279,142.88076928931974,161.65159485993053,143.00450389889755,161.76C143.3457221405222,162.05894474475485,143.11031442844796,164.8310552552452,143.4515326700726,165.13C143.57526727965043,165.2384051400695,143.7721979416115,165.24826010267205,143.89856144124764,165.13C144.38976377383324,164.67029732912113,144.16087536321365,160.24869294571226,144.34559021242268,158.39C144.47663027226082,157.07140955710997,144.64360939320608,155.02,144.79261898359775,155.01999999999998C144.94162857398945,155.02,145.10860769493462,158.39430305521788,145.2396477547728,158.39C145.42436260398185,158.38393438772317,145.53766693555616,153.89666666666668,145.68667652594783,151.64999999999998C145.83568611633953,149.4033333333333,145.9489904479138,144.91606561227684,146.13370529712287,144.91000000000003C146.26474535696104,144.90569694478214,146.4565251462658,146.77812703108052,146.58073406829794,148.27999999999997C146.82813896449113,151.27149787222797,146.7803579432798,161.74777700775797,147.02776283947298,161.76C147.1519717615051,161.7661365183703,147.34375155080983,159.70859044289,147.47479161064803,158.39C147.65950645985708,156.5313070542878,147.737105532614,153.5086929457122,147.92182038182307,151.64999999999998C148.05286044166124,150.33140955710994,148.24282557164233,148.27447083538502,148.36884915299814,148.27999999999997C148.58628476437406,148.28953978037697,148.66686833378148,155.01999999999998,148.81587792417315,158.39C148.96488751456482,161.75999999999996,148.65117819566123,167.91472228813433,149.26290669534822,168.5C149.3914764482839,168.62301047090634,149.58620085694542,168.39159485993054,149.70993546652326,168.5C150.0511537081479,168.79894474475486,149.81574599607367,171.57105525524517,150.1569642376983,171.87C150.2806988472761,171.97840514006953,150.4802583992955,171.7615948599305,150.60399300887335,171.87C150.94521125049795,172.16894474475484,150.90201218965674,175.24000000000004,151.05102178004842,175.24C151.20003137044014,175.24000000000004,151.36701049138532,171.86569694478212,151.49805055122346,171.87C151.68276540043252,171.87606561227685,151.453876989813,178.15029732912114,151.9450793223985,178.61C152.0714428220346,178.7282601026721,152.26837348399573,178.50159485993055,152.39210809357354,178.61C152.7333263351982,178.90894474475493,152.7080968049104,181.98430305521788,152.8391368647486,181.98C153.02385171395764,181.97393438772315,153.13715604553192,177.48666666666665,153.28616563592362,175.24C153.43517522631532,172.99333333333337,153.5964463536329,170.97445592906186,153.73319440709867,168.5C153.90057391345184,165.47126795298243,153.5684946785868,158.97527771186566,154.18022317827374,158.39C154.30879293120938,158.26698952909368,154.50088844981263,158.50826010267207,154.62725194944878,158.39C155.11845428203438,157.93029732912112,154.58307838803827,152.10970267087893,155.07428072062382,151.64999999999998C155.20064422025993,151.53173989732787,155.3927397388632,151.77301047090629,155.52130949179886,151.64999999999998C156.13303799148582,151.06472228813428,155.82858203495397,145.15236848356204,155.96833826297393,141.54000000000002C156.12968040242285,137.36968667748857,155.69999184448145,128.75204490799018,156.41536703414897,128.06C156.54560491718127,127.93400951964543,156.73603230568787,127.94173989732789,156.862395805324,128.06C157.35359813790961,128.51970267087893,156.81822224391357,134.34029732912117,157.30942457649905,134.8C157.43578807613517,134.9182601026721,157.60744375728245,134.80000000000007,157.7564533476741,134.8C157.9054629380658,134.8,158.07324423581684,134.67400951964547,158.20348211884914,134.8C158.91885730851672,135.49204490799022,158.46545929148783,148.27693919668928,158.6505108900242,148.27999999999997C158.78146980571913,148.28216609575867,158.96079160773346,141.53849297323862,159.09753966119925,141.54000000000002C159.26491916755245,141.54184459953171,159.37718892602115,151.64815540046834,159.5445684323743,151.64999999999998C159.68131648584003,151.65150702676132,159.84258761315763,144.91000000000003,159.99159720354933,144.91000000000003C160.14060679394103,144.91000000000005,159.9474236421388,151.19029732912108,160.4386259747244,151.64999999999998C160.56498947436054,151.76826010267206,160.76192013632163,151.75840514006947,160.88565474589944,151.64999999999998C161.22687298752408,151.35105525524511,161.18367392668281,149.4033333333333,161.33268351707449,148.27999999999997C161.48169310746616,147.15666666666667,161.4384940466249,145.20894474475486,161.77971228824953,144.91000000000003C161.90344689782737,144.80159485993053,162.1003775597884,145.0282601026721,162.22674105942457,144.91000000000003C162.71794339201017,144.45029732912118,162.53702177713384,140.6444559290619,162.6737698305996,138.17000000000002C162.84114933695278,135.14126795298242,162.90336299039882,128.06953978037703,163.12079860177468,128.06C163.24682218313052,128.05447083538502,163.43678731311155,131.43430305521787,163.56782737294972,131.43C163.75254222215878,131.42393438772316,163.83014129491576,124.69606561227684,164.01485614412476,124.69C164.1458962039629,124.68569694478208,164.33084485546166,128.06430305521792,164.4618849152998,128.06C164.64659976450886,128.0539343877232,164.72419883726587,123.17869294571224,164.90891368647488,121.32C165.03995374631307,120.00140955710997,165.22991887629408,117.94447083538502,165.35594245764992,117.94999999999999C165.5733780690258,117.959539780377,165.65396163843332,128.06,165.80297122882496,128.06C165.95198081921666,128.06,166.10099040960836,117.95,166.25,117.94999999999999C166.39900959039167,117.95,166.5296492648219,125.0312679529824,166.69702877117504,128.06C166.83377682464078,130.5344559290619,167.00730948888432,132.32554407093815,167.14405754235008,134.8C167.31143704870323,137.82873204701758,167.37365070214932,142.44646887031476,167.59108631352515,144.91000000000003C167.717109894881,146.3378388612609,167.90707502486202,148.28430305521786,168.0381150847002,148.27999999999997C168.22282993390925,148.2739343877232,168.34839580240947,144.0144559290619,168.48514385587524,141.54000000000002C168.6525233622284,138.51126795298245,168.7647931206971,131.43184459953167,168.93217262705028,131.43C169.06892068051602,131.42849297323863,169.19448654901637,136.3113070542878,169.37920139822535,138.17000000000002C169.51024145806352,139.48859044289006,169.70020658804458,141.54552916461503,169.8262301694004,141.54000000000002C170.04366578077628,141.53046021962308,170.05582332919965,131.43953978037703,170.27325894057543,131.43C170.39928252193127,131.42447083538505,170.59426413039458,134.80552916461497,170.72028771175044,134.8C170.9377233231263,134.79046021962304,170.94988087154962,127.15353112968529,171.16731648292554,124.69C171.29334006428132,123.26216113873909,171.27312701247595,121.61894474475483,171.61434525410056,121.32C171.7380798636784,121.21159485993051,171.93501052563946,121.43826010267207,172.0613740252756,121.32C172.55257635786117,120.86029732912114,172.32368794724164,116.43869294571223,172.50840279645064,114.57999999999998C172.6394428562888,113.26140955710993,172.82439150778754,111.20569694478206,172.9554315676257,111.20999999999998C173.14014641683477,111.21606561227682,173.25345074840908,117.94999999999997,173.40246033880075,117.94999999999999C173.55146992919242,117.94999999999997,173.66477426076673,113.06869294571221,173.8494891099758,111.20999999999998C173.98052916981396,109.89140955710995,174.17049429979502,107.834470835385,174.29651788115083,107.83999999999997C174.51395349252675,107.84953978037697,174.52611104095004,115.4864688703147,174.7435466523259,117.94999999999999C174.86957023368174,119.3778388612609,175.0415658331093,121.32000000000002,175.19057542350095,121.32C175.33958501389264,121.32000000000002,175.29638595305138,118.24894474475485,175.637604194676,117.94999999999999C175.76133880425382,117.84159485993051,175.9582694662149,118.0682601026721,176.08463296585103,117.94999999999999C176.57583529843657,117.49029732912109,176.39491368356033,111.20849297323862,176.5316617370261,111.20999999999998C176.69904124337927,111.21184459953163,176.84432831290115,117.52128506903641,176.97869050820114,121.32C177.15209717566586,126.22258807811349,177.25231261191144,133.26741192188652,177.42571927937618,138.17000000000002C177.56008147467614,141.96871493096353,177.70536854419802,145.2512679529824,177.87274805055122,148.27999999999997C178.009496104017,150.7544559290618,178.1707672313346,152.7733333333333,178.3197768217263,155.01999999999998C178.468786412118,157.26666666666665,178.63005753943557,161.76150702676136,178.76680559290133,161.76C178.93418509925448,161.75815540046835,178.9963987527006,154.11353112968536,179.21383436407638,151.64999999999998C179.3398579454322,150.2221611387391,179.53665421321926,148.27386348162966,179.6608631352514,148.27999999999997C179.90826803144452,148.29222299224202,179.86048701023324,161.74777700775795,180.10789190642643,161.76C180.2321008284586,161.76613651837027,180.43071175556938,158.38386348162967,180.55492067760153,158.39C180.80232557379472,158.40222299224203,180.75454455258338,171.85777700775796,181.00194944877654,171.87C181.12615837080867,171.87613651837032,181.31793816011339,169.81859044289,181.44897821995158,168.5C181.63369306916064,166.64130705428778,181.7112921419176,163.61869294571224,181.89600699112663,161.76C182.0270470509648,160.44140955710995,182.19402617191005,159.51333333333335,182.3430357623017,158.39C182.49204535269342,157.26666666666668,182.65902447363857,155.0156969447821,182.79006453347674,155.01999999999998C182.9747793826858,155.02606561227682,183.1061343889569,159.12659703507333,183.23709330465178,161.76C183.4221449031881,165.4811321250199,183.4367171796336,172.24850212777193,183.68412207582682,175.24C183.80833099785897,176.74187296891947,183.98214125661025,178.61000000000004,184.1311508470019,178.61C184.2801604373936,178.61000000000004,184.4291700277853,175.24,184.57817961817693,175.24C184.72718920856866,175.24000000000004,184.89918480799616,178.61552916461497,185.02520838935197,178.61C185.24264400072786,178.60046021962305,185.30485765417387,168.50184459953167,185.47223716052702,168.5C185.60898521399278,168.49849297323863,185.73455108249308,173.3813070542878,185.91926593170209,175.24C186.05030599154028,176.55859044289005,186.21728511248548,178.61000000000004,186.36629470287713,178.61C186.51530429326883,178.61000000000004,186.682283414214,175.23569694478212,186.81332347405217,175.24C186.9980383232612,175.24606561227682,186.76914991264164,181.52029732912112,187.2603522452272,181.98C187.38671574486335,182.09826010267207,187.57584106717616,182.10809649096691,187.70738101640228,181.98C188.51511113245886,181.1934146765008,187.95250599956506,165.1340087277007,188.15440978757732,165.13C188.28222267215602,165.127462320762,188.11023622616673,171.41029732912108,188.60143855875236,171.87C188.72780205838845,171.98826010267203,188.92473272034957,171.76159485993048,189.04846732992738,171.87C189.38968557155204,172.16894474475487,189.3644560412643,175.2443030552179,189.49549610110247,175.24C189.68021095031153,175.2339343877232,189.79351528188576,170.74666666666664,189.9425248722775,168.5C190.09153446266916,166.25333333333333,190.20483879424353,163.6186929457122,190.38955364345253,161.76C190.52059370329067,160.44140955710995,190.6875728242359,159.51333333333335,190.83658241462757,158.39C190.9855920050193,157.26666666666668,191.15257112596447,156.33859044289002,191.28361118580264,155.01999999999998C191.4683260350117,153.16130705428776,191.54592510776862,150.1386929457122,191.73063995697768,148.27999999999997C191.8616800168158,146.96140955710993,192.02865913776105,144.91000000000003,192.17766872815272,144.91000000000003C192.3266783185444,144.91000000000003,192.49365743948962,148.28430305521786,192.62469749932777,148.27999999999997C192.80941234853682,148.2739343877232,192.92271668011114,143.78666666666672,193.0717262705028,141.54000000000002C193.22073586089454,139.29333333333338,193.02755270909233,135.25970267087888,193.51875504167788,134.8C193.64511854131405,134.68173989732793,193.83942031321678,134.9182601026721,193.96578381285292,134.8C194.45698614543855,134.34029732912117,194.22809773481893,128.06606561227684,194.41281258402796,128.06C194.5438526438661,128.05569694478208,194.71083176481133,130.3066666666667,194.859841355203,131.43C195.00885094559473,132.55333333333337,195.15786053598643,133.6766666666667,195.30687012637807,134.8C195.4558797167698,135.92333333333337,195.62787531619733,138.175529164615,195.7538988975531,138.17000000000002C195.971334508929,138.16046021962308,195.5891991690412,128.64527771186567,196.20092766872816,128.06C196.32949742166383,127.9369895290937,196.49894684951153,128.06,196.6479564399032,128.06C196.79696603029487,128.06,196.9686217114421,127.94173989732789,197.09498521107827,128.06C197.58618754366387,128.51970267087893,197.0508116496677,134.34029732912109,197.5420139822533,134.8C197.66837748188942,134.91826010267206,197.86267925379215,134.6817398973279,197.98904275342832,134.8C198.4802450860138,135.25970267087885,198.2513566753943,139.6813070542878,198.43607152460336,141.54000000000002C198.56711158444156,142.85859044289006,198.7520602359403,143.59140955711,198.88310029577846,144.91000000000003C199.0678151449875,146.76869294571222,199.14541421774445,151.64393438772314,199.33012906695348,151.64999999999998C199.4611691267917,151.6543030552179,199.62814824773682,149.4033333333333,199.77715783812852,148.27999999999997C199.9261674285202,147.15666666666667,200.10074113073208,144.90321757864237,200.22418660930356,144.91000000000003C200.5252861078758,144.92654320347367,200.45323431181592,165.12519021952068,200.67121538047863,165.13C200.7971895952058,165.13277963734467,200.62704181906815,158.84970267087888,201.11824415165367,158.39C201.2446076512898,158.27173989732788,201.41626333243704,158.39000000000001,201.5652729228287,158.39C201.71428251322044,158.39000000000001,201.8885670844259,158.28159485993046,202.01230169400375,158.39C202.35351993562836,158.68894474475485,202.31032087478715,161.76000000000002,202.45933046517882,161.76C202.60834005557058,161.76000000000002,202.7821503143217,158.38386348162967,202.90635923635386,158.39C203.15376413254705,158.40222299224203,203.19204586808002,171.869109584796,203.3533880075289,171.87C203.4931442355489,171.87077128684865,203.58298116732806,164.22353112968528,203.80041677870395,161.76C203.92644036005976,160.33216113873908,204.0984359594874,159.51333333333335,204.24744554987902,158.39C204.39645514027075,157.26666666666668,204.54546473066245,156.14333333333335,204.69447432105406,155.01999999999998C204.8434839114458,153.89666666666668,205.01046303239093,151.6456969447821,205.1415030922291,151.64999999999998C205.32621794143816,151.65606561227685,205.09732953081854,157.93029732912106,205.58853186340414,158.39C205.71489536304023,158.50826010267207,205.909197134943,158.50826010267207,206.03556063457916,158.39C206.52676296716473,157.93029732912112,206.29787455654528,151.65606561227685,206.48258940575425,151.64999999999998C206.61362946559242,151.64569694478212,206.8035945955735,155.02552916461497,206.9296181769293,155.01999999999998C207.14705378830513,155.01046021962298,207.2092674417511,147.93873204701757,207.3766469481043,144.91000000000003C207.51339500157005,142.43554407093814,207.63896087007032,138.17606561227686,207.82367571927935,138.17000000000002C207.9547157791175,138.1656969447821,207.92948624882973,141.24105525524516,208.27070449045442,141.54000000000002C208.39443910003226,141.6484051400695,208.58916350869382,141.41698952909368,208.71773326162946,141.54000000000002C209.32946176131642,142.12527771186572,208.94732642142864,151.640460219623,209.1647620328045,151.64999999999998C209.29078561416034,151.65552916461496,209.46278121358787,148.28,209.61179080397955,148.27999999999997C209.7608003943713,148.28000000000003,209.92777951531642,150.33140955710994,210.05881957515462,151.64999999999998C210.24353442436367,153.5086929457122,210.35683875593799,158.39,210.50584834632966,158.39C210.65485793672136,158.39,210.76816226829567,153.50869294571223,210.9528771175047,151.64999999999998C211.08391717734284,150.33140955710996,211.05868764705514,148.5789447447549,211.39990588867974,148.27999999999997C211.52364049825763,148.1715948599305,211.723200050277,148.1715948599305,211.8469346598548,148.27999999999997C212.18815290147953,148.57894474475486,212.14495384063818,151.65,212.29396343102985,151.64999999999998C212.44297302142155,151.65,212.59198261181322,149.4033333333333,212.7409922022049,148.27999999999997C212.89000179259656,147.15666666666667,213.03901138298826,144.91000000000005,213.18802097337993,144.91000000000003C213.33703056377163,144.91000000000003,213.29383150293037,147.98105525524514,213.635049744555,148.27999999999997C213.75878435413287,148.3884051400695,213.95350876279437,148.40301047090628,214.08207851573005,148.27999999999997C214.69380701541695,147.69472228813424,213.91737878721815,138.7552777118657,214.5291072869051,138.17000000000002C214.6576770398407,138.04698952909368,214.84977255844402,138.05173989732796,214.97613605808013,138.17000000000002C215.4673383906657,138.6297026708789,215.28641677578943,144.9115070267614,215.4231648292552,144.91000000000003C215.5905443356084,144.9081554004684,215.65275798905438,134.80953978037704,215.87019360043024,134.8C215.99621718178608,134.79447083538506,215.97600412998062,137.87105525524515,216.31722237160528,138.17000000000002C216.4409569811831,138.2784051400695,216.6378876431442,138.28826010267213,216.7642511427803,138.17000000000002C217.25545347536584,137.71029732912112,217.0265650647464,131.43606561227688,217.2112799139554,131.43C217.3423199737936,131.42569694478212,217.5322851037746,134.805529164615,217.6583086851304,134.8C217.87574429650635,134.79046021962307,217.93795794995228,124.69184459953166,218.10533745630545,124.69C218.2420855097712,124.68849297323862,218.3676513782715,129.5713070542878,218.5523662274805,131.43C218.68340628731863,132.74859044289002,218.87337141729972,133.37216113873916,218.99939499865553,134.8C219.21683061003145,137.26353112968536,219.2289881584548,142.44646887031476,219.4464237698306,144.91000000000003C219.57244735118644,146.3378388612609,219.76742895964983,146.85216113873915,219.89345254100564,148.27999999999997C220.11088815238156,150.74353112968527,220.12304570080485,158.38046021962302,220.34048131218069,158.39C220.46650489353652,158.395529164615,220.63850049296406,156.14333333333332,220.78751008335573,155.01999999999998C220.93651967374745,153.89666666666665,221.11032993249867,151.6438634816297,221.2345388545308,151.64999999999998C221.48194375072396,151.662222992242,221.4965160271695,161.40886787498007,221.68156762570584,165.13C221.81252654140073,167.76340296492668,221.63739406429536,171.41029732912116,222.12859639688088,171.87C222.254959896517,171.98826010267206,222.42661557766425,171.87,222.57562516805592,171.87C222.7246347584476,171.87,222.8736443488393,171.87,223.022653939231,171.87C223.1716635296227,171.87,223.34111295747041,171.99301047090634,223.46968271040603,171.87C224.08141121009302,171.28472228813433,223.69927587020527,161.769539780377,223.91671148158107,161.76C224.0427350629369,161.75447083538504,224.21473066236447,165.13000000000002,224.36374025275612,165.13C224.51274984314784,165.13000000000002,224.68474544257532,161.754470835385,224.8107690239312,161.76C225.0282046353071,161.769539780377,225.10878820471453,171.87000000000003,225.25779779510623,171.87C225.40680738549793,171.87000000000003,225.48739095490538,164.22353112968528,225.70482656628124,161.76C225.8308501476371,160.3321611387391,225.81063709583162,158.68894474475485,226.15185533745628,158.39C226.27558994703415,158.2815948599305,226.47252060899524,158.50826010267207,226.59888410863138,158.39C227.09008644121695,157.93029732912103,226.86119803059734,153.5086929457122,227.0459128798064,151.64999999999998C227.1769529396445,150.3314095571099,227.34393206058974,149.4033333333333,227.49294165098144,148.27999999999997C227.6419512413731,147.15666666666667,227.59875218053182,145.20894474475492,227.93997042215648,144.91000000000003C228.0637050317343,144.80159485993053,228.23798960293985,144.91000000000003,228.38699919333155,144.91000000000003C228.53600878372322,144.91000000000003,228.68501837411492,144.91000000000003,228.8340279645066,144.91000000000003C228.98303755489832,144.91000000000005,229.15469323604546,145.0282601026721,229.28105673568163,144.91000000000003C229.7722590682672,144.45029732912118,229.60027262227803,138.167462320762,229.72808550685667,138.17000000000002C229.92998929486893,138.17400872770074,229.3673841619752,154.23341467650084,230.17511427803174,155.01999999999998C230.3066542272579,155.14809649096688,230.49840843962897,155.1284051400695,230.62214304920678,155.01999999999998C230.96336129083147,154.72105525524515,230.94560316213318,151.64348462894964,231.06917182038183,151.64999999999998C231.34432585706108,151.6645079720891,231.24104655487756,168.48549202791088,231.51620059155687,168.5C231.63976924980557,168.50651537105034,231.81421977234027,165.13000000000002,231.9632293627319,165.13C232.11223895312364,165.13000000000002,232.06903989228232,168.2010552552451,232.41025813390698,168.5C232.53399274348482,168.6084051400695,232.7082773146904,168.5,232.85728690508202,168.5C233.00629649547372,168.50000000000003,233.17795217662098,168.6182601026721,233.30431567625706,168.5C233.79551800884263,168.04029732912113,233.61459639396625,164.23445592906182,233.75134444743207,161.76C233.91872395378525,158.7312679529824,233.58664471892027,152.23527771186568,234.19837321860717,151.64999999999998C234.32694297154282,151.52698952909367,234.52166738020438,151.54159485993048,234.64540198978221,151.64999999999998C234.98662023140685,151.9489447447548,234.75121251933257,154.72105525524512,235.09243076095723,155.01999999999998C235.2161653705351,155.12840514006948,235.40922164909998,155.14599048035456,235.53945953213227,155.01999999999998C236.2548347217999,154.3279550920098,235.73908340711418,144.53149787222807,235.98648830330734,141.54000000000002C236.1106972253395,140.0381270310806,236.09229883285775,138.46894474475485,236.43351707448238,138.17000000000002C236.55725168406022,138.06159485993052,236.75681123607959,138.06159485993052,236.88054584565742,138.17000000000002C237.22176408728203,138.46894474475488,237.1785650264408,141.54000000000005,237.32757461683246,141.54000000000002C237.47658420722422,141.54000000000005,237.6485798066517,138.16447083538503,237.77460338800753,138.17000000000002C237.99203899938337,138.17953978037698,238.0872699638826,144.48128506903643,238.22163215918258,148.27999999999997C238.39503882664724,153.18258807811347,238.39350689367828,165.11549202791082,238.66866093035762,165.13C238.7922295886063,165.13651537105034,238.966680111141,162.88333333333335,239.11568970153266,161.76C239.2646992919244,160.63666666666668,239.2215002310831,158.68894474475485,239.56271847270773,158.39C239.68645308228557,158.28159485993046,239.88117749094715,158.26698952909368,240.00974724388277,158.39C240.62147574356976,158.9752777118657,240.23934040368198,168.490460219623,240.4567760150578,168.5C240.58279959641368,168.505529164615,240.7727647263947,165.1256969447821,240.90380478623285,165.13C241.08851963544194,165.13606561227687,241.16611870819892,171.8639343877232,241.35083355740792,171.87C241.48187361724612,171.8743030552179,241.6668222687448,169.81859044289,241.79786232858297,168.5C241.98257717779202,166.64130705428778,242.060176250549,161.76606561227683,242.244891099758,161.76C242.37593115959618,161.75569694478207,242.5608798110949,165.1343030552179,242.69191987093305,165.13C242.87663472014214,165.12393438772318,242.95423379289912,160.24869294571224,243.13894864210812,158.39C243.26998870194632,157.07140955710994,243.2447591716585,155.31894474475482,243.58597741328316,155.01999999999998C243.709712022861,154.9115948599305,243.906642684822,155.13826010267204,244.03300618445817,155.01999999999998C244.5242085170437,154.56029732912108,244.3490760399383,150.9134029649267,244.48003495563322,148.27999999999997C244.66508655416956,144.55886787498002,244.77805413641659,134.8,244.92706372680826,134.8C245.07607331719993,134.8,244.65871730831574,147.5879550920098,245.37409249798333,148.27999999999997C245.50433038101568,148.40599048035457,245.69475776952223,148.39826010267205,245.82112126915837,148.27999999999997C246.31232360174397,147.8202973291211,246.08343519112435,143.39869294571224,246.2681500403334,141.54000000000002C246.39919010017155,140.22140955710998,246.58413875167028,139.48859044289006,246.71517881150845,138.17000000000002C246.8998936607175,136.3113070542878,246.97749273347455,131.43606561227688,247.16220758268352,131.43C247.29324764252175,131.42569694478212,247.46022676346695,133.67666666666668,247.60923635385856,134.8C247.75824594425023,135.9233333333334,247.92522506519543,136.85140955710997,248.0562651250336,138.17000000000002C248.24097997424263,140.02869294571227,248.01209156362307,144.4502973291211,248.50329389620865,144.91000000000003C248.62965739584482,145.02826010267208,248.82658805780588,144.8015948599305,248.95032266738372,144.91000000000003C249.29154090900838,145.2089447447549,249.2713278572029,148.28552916461496,249.39735143855876,148.27999999999997C249.6147870499347,148.270460219623,249.626944598358,140.63353112968534,249.8443802097338,138.17000000000002C249.9704037910896,136.74216113873914,250.1603689210707,134.79569694478212,250.29140898090884,134.8C250.47612383011796,134.80606561227685,250.60747883638905,138.90659703507336,250.7384377520839,141.54000000000002C250.92348935062026,145.26113212501994,250.9380616270658,152.02850212777196,251.18546652325895,155.01999999999998C251.30967544529113,156.52187296891947,251.29127705280942,158.09105525524515,251.632495294434,158.39C251.75622990401178,158.49840514006945,251.9531605659729,158.50826010267207,252.079524065609,158.39C252.57072639819458,157.93029732912112,252.3418379875751,153.50869294571223,252.5265528367841,151.64999999999998C252.6575928966223,150.33140955710996,252.84755802660337,148.27447083538502,252.97358160795915,148.27999999999997C253.19101721933498,148.28953978037694,253.20317476775833,158.38046021962296,253.42061037913416,158.39C253.54663396049,158.395529164615,253.74161556895342,155.01447083538503,253.8676391503092,155.01999999999998C254.08507476168515,155.029539780377,254.14728841513113,165.12815540046833,254.31466792148427,165.13C254.45141597495007,165.13150702676137,254.57698184345028,160.24869294571224,254.7616966926593,158.39C254.89273675249748,157.07140955710994,254.86750722220972,155.31894474475487,255.20872546383436,155.01999999999998C255.3324600734122,154.9115948599305,255.50674464461775,155.02,255.6557542350094,155.01999999999998C255.80476382540112,155.02,255.9764195065483,155.13826010267204,256.1027830061845,155.01999999999998C256.5939853387701,154.56029732912106,256.40080218696784,148.27999999999997,256.54981177735954,148.27999999999997C256.69882136775124,148.27999999999997,256.86009249506884,155.0215070267613,256.9968405485346,155.01999999999998C257.16422005488766,155.0181554004683,256.83214082002263,145.49527771186578,257.44386931970956,144.91000000000003C257.57243907264524,144.78698952909372,257.76716348130674,144.8015948599305,257.8908980908846,144.91000000000003C258.23211633250924,145.2089447447549,258.2068868022215,148.28430305521783,258.3379268620597,148.27999999999997C258.5226417112687,148.27393438772313,258.64820757976895,141.53849297323862,258.78495563323474,141.54000000000002C258.95233513958794,141.54184459953171,258.62025590472285,151.0647222881343,259.2319844044098,151.64999999999998C259.3605541573454,151.77301047090629,259.55264967594866,151.76826010267206,259.6790131755848,151.64999999999998C260.1702155081704,151.1902973291211,259.94132709755087,144.9160656122769,260.12604194675987,144.91000000000003C260.257082006598,144.90569694478214,260.4420306580967,146.96140955710993,260.5730707179349,148.27999999999997C260.75778556714397,150.1386929457122,260.83538463990095,155.01393438772314,261.02009948910995,155.01999999999998C261.1511395489481,155.0243030552179,261.3411046789292,151.644470835385,261.467128260285,151.64999999999998C261.6845638716609,151.65953978037697,261.30242853177316,161.1747222881343,261.9141570314601,161.76C262.04272678439577,161.8830104709063,262.23482230299896,161.64173989732785,262.36118580263513,161.76C262.85238813522085,162.21970267087892,262.6234997246012,168.49393438772316,262.8082145738102,168.5C262.9392546336483,168.5043030552179,263.12921976362946,166.5578388612609,263.2552433449852,165.13C263.47267895636116,162.6664688703147,263.0905436164733,155.60527771186568,263.70227211616026,155.01999999999998C263.83084186909593,154.89698952909367,264.0002912969437,155.02,264.1493008873353,155.01999999999998C264.298310477727,155.02,264.4725950489326,155.1284051400695,264.59632965851034,155.01999999999998C264.9375479001351,154.72105525524515,264.91231836984724,152.96859044289002,265.0433584296854,151.64999999999998C265.22807327889444,149.79130705428778,265.3413776104688,144.91000000000003,265.4903872008605,144.91000000000003C265.6393967912522,144.91000000000003,265.80645705634066,151.65216609575862,265.9374159720355,151.64999999999998C266.12246757057187,151.6469391966893,266.13703984701743,141.16149787222804,266.38444474321057,138.17000000000002C266.50865366524266,136.66812703108056,266.70043345454735,136.11859044289005,266.83147351438555,134.8C267.01618836359455,132.94130705428782,266.7872999529751,128.51970267087893,267.27850228556065,128.06C267.40486578519676,127.94173989732792,267.5991675570995,127.94173989732789,267.7255310567357,128.06C268.2167333893213,128.51970267087893,267.9878449787018,132.9413070542878,268.17255982791073,134.8C268.3035998877489,136.11859044289002,268.27837035746114,137.87105525524518,268.6195885990858,138.17000000000002C268.7433232086636,138.2784051400695,268.94288276068306,138.2784051400695,269.0666173702608,138.17000000000002C269.4078356118855,137.87105525524518,269.36463655104427,134.80000000000004,269.51364614143586,134.8C269.66265573182756,134.80000000000004,269.81166532221926,138.17000000000004,269.9606749126109,138.17000000000002C270.10968450300265,138.17000000000004,270.2766636239478,134.79569694478212,270.40770368378594,134.8C270.59241853299505,134.80606561227688,270.7057228645692,141.54,270.85473245496104,141.54000000000002C271.00374204535274,141.54000000000002,271.1527516357444,134.8,271.3017612261361,134.8C271.4507708165277,134.8,271.5640751481021,139.68130705428783,271.7487899973111,141.54000000000002C271.87983005714926,142.85859044289003,272.0468091780945,144.91000000000005,272.19581876848616,144.91000000000003C272.3448283588779,144.91000000000005,272.51180747982295,142.85859044289006,272.6428475396612,141.54000000000002C272.82756238887026,139.6813070542878,272.5986739782507,135.25970267087894,273.08987631083625,134.8C273.21623981047236,134.6817398973279,273.38789549161964,134.80000000000007,273.5369050820113,134.8C273.68591467240304,134.8,273.8601992436085,134.69159485993052,273.98393385318633,134.8C274.32515209481096,135.09894474475487,274.28195303396967,138.17000000000002,274.43096262436137,138.17000000000002C274.57997221475307,138.17000000000002,274.72898180514477,135.92333333333335,274.87799139553647,134.8C275.02700098592817,133.67666666666668,275.19398010687337,131.4256969447821,275.3250201667115,131.43C275.5097350159206,131.43606561227688,275.5873340886775,136.3113070542878,275.7720489378865,138.17000000000002C275.9030889977247,139.48859044289006,276.0700681186699,141.54,276.21907770906154,141.54000000000002C276.3680872994532,141.54000000000002,276.5170968898449,138.17000000000002,276.66610648023664,138.17000000000002C276.8151160706283,138.17000000000002,276.9820951915735,140.22140955710998,277.1131352514117,141.54000000000002C277.29785010062074,143.39869294571224,277.411154432195,148.27999999999997,277.5601640225867,148.27999999999997C277.70917361297836,148.27999999999997,277.5159904611763,141.99970267087895,278.00719279376176,141.54000000000002C278.1335562933979,141.4217398973279,278.3256518120012,141.66301047090636,278.4542215649368,141.54000000000002C279.06595006462373,140.95472228813432,278.2895218364249,132.0152777118657,278.90125033611184,131.43C279.0298200890475,131.3069895290937,279.19926951689524,131.43000000000004,279.3482791072869,131.43C279.49728869767864,131.43000000000004,279.67157326888406,131.5384051400695,279.7953078784619,131.43C280.1365261200866,131.13105525524523,280.1163130682812,129.4878388612609,280.242336649637,128.06C280.45977226101286,125.59646887031468,280.4719298094363,117.95953978037697,280.68936542081207,117.94999999999999C280.81538900216793,117.94447083538502,281.01037061063136,121.32552916461499,281.1363941919871,121.32C281.35382980336306,121.31046021962302,281.3659873517863,111.21953978037698,281.58342296316215,111.20999999999998C281.709446544518,111.20447083538501,281.899411674499,113.26140955710994,282.0304517343372,114.57999999999998C282.2151665835462,116.43869294571222,282.32847091512053,119.07333333333334,282.47748050551223,121.32C282.62649009590393,123.56666666666665,282.79355036099236,125.42659703507331,282.9245092766873,128.06C283.10956087522356,131.78113212501992,282.65616285819476,140.8479550920098,283.3715380478623,141.54000000000002C283.50177593089467,141.66599048035462,283.6695572286458,141.54000000000005,283.8185668190374,141.54000000000002C283.9675764094291,141.54000000000005,284.14186098063465,141.43159485993053,284.26559559021246,141.54000000000002C284.60681383183714,141.83894474475488,284.5815843015493,144.9143030552179,284.7126243613875,144.91000000000003C284.89733921059656,144.9039343877232,285.02290507909674,140.6444559290619,285.1596531325625,138.17000000000002C285.3270326389157,135.14126795298242,285.38924629236163,128.06953978037697,285.6066819037375,128.06C285.7327054850934,128.05447083538502,285.9276870935568,131.43552916461502,286.0537106749126,131.43C286.27114628628857,131.420460219623,286.28330383471183,123.78353112968531,286.50073944608766,121.32C286.6267630274435,119.89216113873913,286.8235592952306,119.45187296891946,286.9477682172627,117.94999999999999C287.1951731134559,114.95850212777198,287.2097453899014,108.19113212501995,287.39479698843775,104.47000000000003C287.52575590413267,101.83659703507335,287.3506234270273,98.18970267087896,287.8418257596128,97.73000000000002C287.9681892592489,97.61173989732792,288.16249103115166,97.8482601026721,288.28885453078783,97.73000000000002C288.7800568633735,97.27029732912116,288.5511684527538,92.84869294571224,288.7358833019629,90.99000000000001C288.86692336180107,89.67140955710997,289.0518720132997,87.61569694478212,289.1829120731379,87.62C289.36762692234703,87.62606561227686,289.44522599510407,92.50130705428782,289.629940844313,94.36000000000001C289.7609809041512,95.67859044289006,289.9279600250964,96.60666666666668,290.07696961548805,97.73000000000002C290.2259792058798,98.85333333333337,290.3929583268249,99.78140955710998,290.5239983866631,101.10000000000002C290.7087132358721,102.95869294571224,290.8432142732595,105.08594534810562,290.97102715783814,107.83999999999997C291.1729309458504,112.19053217399171,291.2446492615485,119.78741192188653,291.4180559290132,124.69C291.5524181243131,128.48871493096354,291.64764908881244,134.79046021962307,291.8650847001882,134.8C291.9911082815441,134.805529164615,292.1631038809716,132.55333333333337,292.31211347136326,131.43C292.461123061755,130.3066666666667,292.6281021827001,128.0556969447821,292.7591422425383,128.06C292.9438570917474,128.06606561227687,293.0571614233216,132.5533333333333,293.2061710137134,134.8C293.35518060410516,137.0466666666667,293.5041901944968,139.29333333333335,293.65319978488844,141.54000000000002C293.80220937528003,143.78666666666663,293.95121896567167,146.03333333333327,294.1002285560634,148.27999999999997C294.2492381464551,150.52666666666664,294.05605499465287,154.56029732912106,294.54725732723847,155.01999999999998C294.6736208268745,155.13826010267204,294.86792259877734,154.90173989732784,294.99428609841357,155.01999999999998C295.48548843099917,155.4797026708789,295.31350198501,161.76253767923805,295.4413148695886,161.76C295.64321865760087,161.75599127229924,295.61318960408437,144.9245079720892,295.88834364076365,144.91000000000003C296.01191229901235,144.9034846289497,295.994154170314,147.9810552552451,296.3353724119387,148.27999999999997C296.4591070215165,148.38840514006947,296.658666573536,148.17159485993048,296.78240118311373,148.27999999999997C297.1236194247384,148.5789447447548,297.09838989445063,150.33140955710996,297.2294299542888,151.64999999999998C297.41414480349783,153.50869294571223,297.1852563928783,157.9302973291211,297.6764587254638,158.39C297.80282222509993,158.50826010267207,297.99975288706105,158.2815948599305,298.12348749663886,158.39C298.4647057382635,158.68894474475485,298.44449268645803,161.76552916461497,298.5705162678139,161.76C298.7879518791898,161.75046021962297,298.8685354485973,155.01999999999998,299.017545038989,151.64999999999998C299.16655462938076,148.27999999999997,299.29719430381084,144.56873204701756,299.46457381016404,141.54000000000002C299.60132186362983,139.06554407093816,299.4204002487536,135.25970267087894,299.9116025813391,134.8C300.0379660809752,134.6817398973279,300.2300615995785,134.92301047090638,300.3586313525141,134.8C300.970359852201,134.21472228813434,300.5882245123133,124.69953978037701,300.80566012368917,124.69C300.93168370504503,124.68447083538503,301.12164883502606,128.06430305521792,301.2526888948642,128.06C301.43740374407326,128.0539343877232,301.55070807564755,123.56666666666665,301.69971766603925,121.32C301.84872725643095,119.07333333333334,301.9977368468226,116.82666666666665,302.1467464372143,114.5799
gitextract__y584mo5/
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── docs/
│ ├── d3-interpolate-path.js
│ ├── example.css
│ ├── examples.js
│ ├── index.html
│ └── v1/
│ ├── d3-interpolate-path-v1.1.1.js
│ ├── example.css
│ ├── examples.js
│ └── index.html
├── index.js
├── package.json
├── rollup.config.js
├── src/
│ ├── .babelrc
│ ├── interpolatePath.js
│ └── split.js
└── test/
├── interpolatePath-test.js
└── interpolatePathCommands-test.js
SYMBOL INDEX (59 symbols across 8 files)
FILE: docs/d3-interpolate-path.js
function ownKeys (line 7) | function ownKeys(object, enumerableOnly) {
function _objectSpread2 (line 25) | function _objectSpread2(target) {
function _typeof (line 45) | function _typeof(obj) {
function _defineProperty (line 61) | function _defineProperty(obj, key, value) {
function _extends (line 76) | function _extends() {
function _unsupportedIterableToArray (line 94) | function _unsupportedIterableToArray(o, minLen) {
function _arrayLikeToArray (line 103) | function _arrayLikeToArray(arr, len) {
function _createForOfIteratorHelper (line 111) | function _createForOfIteratorHelper(o, allowArrayLike) {
function decasteljau (line 178) | function decasteljau(points, t) {
function pointsToCommand (line 223) | function pointsToCommand(points) {
function splitCurveAsPoints (line 261) | function splitCurveAsPoints(points, segmentCount) {
function splitCurve (line 302) | function splitCurve(commandStart, commandEnd, segmentCount) {
function arrayOfLength (line 339) | function arrayOfLength(length, value) {
function commandToString (line 355) | function commandToString(command) {
function convertToSameType (line 382) | function convertToSameType(aCommand, bCommand) {
function splitSegment (line 437) | function splitSegment(commandStart, commandEnd, segmentCount) {
function extend (line 471) | function extend(commandsToExtend, referenceCommands, excludeSegment) {
function pathCommandsFromString (line 546) | function pathCommandsFromString(d) {
function interpolatePathCommands (line 595) | function interpolatePathCommands(aCommandsInput, bCommandsInput, interpo...
function interpolatePath (line 727) | function interpolatePath(a, b, interpolateOptions) {
FILE: docs/examples.js
function loopPathBasic (line 19) | function loopPathBasic(path, dPath1, dPath2, loopForever) {
function loopPath (line 43) | function loopPath(path, dPath1, dPath2, pathTextRoot, svg, excludeSegmen...
function mainExample (line 84) | function mainExample() {
function formatDString (line 310) | function formatDString(str) {
function showPathPoints (line 314) | function showPathPoints(svg, transition) {
function showDValues (line 357) | function showDValues(root, dLine1, dLine2, pathNode, transition) {
function pathStringToExtent (line 385) | function pathStringToExtent(str) {
function makeExample (line 396) | function makeExample(d, useInterpolatePath) {
FILE: docs/v1/d3-interpolate-path-v1.1.1.js
function commandObject (line 28) | function commandObject(commandString) {
function commandToString (line 46) | function commandToString(command) {
function convertToSameType (line 75) | function convertToSameType(aCommand, bCommand) {
function extend (line 133) | function extend(commandsToExtend, referenceCommands, numPointsToExtend) {
function interpolatePath (line 211) | function interpolatePath(a, b) {
FILE: docs/v1/examples.js
function loopPathBasic (line 20) | function loopPathBasic(path, dPath1, dPath2, loopForever) {
function loopPath (line 44) | function loopPath(path, dPath1, dPath2, pathTextRoot, svg, excludeSegmen...
function mainExample (line 85) | function mainExample() {
function formatDString (line 311) | function formatDString(str) {
function showPathPoints (line 315) | function showPathPoints(svg, transition) {
function showDValues (line 358) | function showDValues(root, dLine1, dLine2, pathNode, transition) {
function pathStringToExtent (line 386) | function pathStringToExtent(str) {
function makeExample (line 397) | function makeExample(d, useInterpolatePath) {
FILE: src/interpolatePath.js
function arrayOfLength (line 25) | function arrayOfLength(length, value) {
function commandToString (line 39) | function commandToString(command) {
function convertToSameType (line 65) | function convertToSameType(aCommand, bCommand) {
function splitSegment (line 123) | function splitSegment(commandStart, commandEnd, segmentCount) {
function extend (line 164) | function extend(commandsToExtend, referenceCommands, excludeSegment) {
function pathCommandsFromString (line 268) | function pathCommandsFromString(d) {
function interpolatePathCommands (line 320) | function interpolatePathCommands(
function interpolatePath (line 449) | function interpolatePath(a, b, interpolateOptions) {
FILE: src/split.js
function decasteljau (line 11) | function decasteljau(points, t) {
function pointsToCommand (line 54) | function pointsToCommand(points) {
function splitCurveAsPoints (line 88) | function splitCurveAsPoints(points, segmentCount) {
function splitCurve (line 133) | function splitCurve(commandStart, commandEnd, segmentCount) {
FILE: test/interpolatePath-test.js
constant APPROX_MAX_T (line 4) | const APPROX_MAX_T = 0.999999999999;
constant MIN_T (line 5) | const MIN_T = 0;
function pathToItems (line 8) | function pathToItems(path) {
function approximatelyEqual (line 17) | function approximatelyEqual(path1, path2) {
FILE: test/interpolatePathCommands-test.js
constant APPROX_MAX_T (line 4) | const APPROX_MAX_T = 0.999999999999;
function approximatelyEqual (line 7) | function approximatelyEqual(path1Commands, path2Commands) {
Condensed preview — 22 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (353K chars).
[
{
"path": ".eslintignore",
"chars": 17,
"preview": "rollup.config.js\n"
},
{
"path": ".eslintrc.js",
"chars": 336,
"preview": "module.exports = {\n env: {\n browser: true,\n es6: true,\n node: true,\n },\n extends: ['eslint:recommended', 'pr"
},
{
"path": ".gitignore",
"chars": 109,
"preview": ".DS_Store\nbuild/\nexample/d3-scale-interactive.js\nexample/d3-scale-interactive.css\nnode_modules\nnpm-debug.log\n"
},
{
"path": ".npmignore",
"chars": 18,
"preview": "build/*.zip\ntest/\n"
},
{
"path": "LICENSE",
"chars": 1471,
"preview": "Copyright 2016, Peter Beshai\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without mo"
},
{
"path": "README.md",
"chars": 4956,
"preview": "# d3-interpolate-path\n\n[](https://badge.fury.io/js/d3-in"
},
{
"path": "docs/d3-interpolate-path.js",
"chars": 25209,
"preview": "(function (global, factory) {\ntypeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :\ntypeof d"
},
{
"path": "docs/example.css",
"chars": 1056,
"preview": ".main-header {\n margin: 0 0 4px;\n}\n\n.main-link a {\n color: #888;\n}\n.main-link {\n margin-top: 0;\n}\n\nbody {\n font: 14p"
},
{
"path": "docs/examples.js",
"chars": 23264,
"preview": "/**\n * Apologies for this code. It's kind of hacked together to quickly demonstrate things.\n */\nvar exampleWidth = 250;\n"
},
{
"path": "docs/index.html",
"chars": 1283,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <title>d3-interpolate-path</title>\n <meta charset=\"utf-8\">\n <link rel=\"stylesh"
},
{
"path": "docs/v1/d3-interpolate-path-v1.1.1.js",
"chars": 9218,
"preview": "(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require("
},
{
"path": "docs/v1/example.css",
"chars": 1056,
"preview": ".main-header {\n margin: 0 0 4px;\n}\n\n.main-link a {\n color: #888;\n}\n.main-link {\n margin-top: 0;\n}\n\nbody {\n font: 14p"
},
{
"path": "docs/v1/examples.js",
"chars": 23312,
"preview": "/**\n * Apologies for this code. It's kind of hacked together to quickly demonstrate things.\n */\nvar version = 'v1.1.1';\n"
},
{
"path": "docs/v1/index.html",
"chars": 1221,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <title>d3-interpolate-path v1.1.1</title>\n <meta charset=\"utf-8\">\n <link rel=\""
},
{
"path": "index.js",
"chars": 124,
"preview": "export {\n default as interpolatePath,\n interpolatePathCommands,\n pathCommandsFromString,\n} from './src/interpolatePat"
},
{
"path": "package.json",
"chars": 1756,
"preview": "{\n \"name\": \"d3-interpolate-path\",\n \"version\": \"2.3.0\",\n \"description\": \"Interpolates path `d` attribute smoothly when"
},
{
"path": "rollup.config.js",
"chars": 1023,
"preview": "// rollup.config.js\nimport resolve from 'rollup-plugin-node-resolve';\nimport babel from 'rollup-plugin-babel';\n\nvar glob"
},
{
"path": "src/.babelrc",
"chars": 69,
"preview": "{\n \"presets\": [\n [\"@babel/env\", {\"modules\": false}]\n ]\n }"
},
{
"path": "src/interpolatePath.js",
"chars": 16271,
"preview": "import splitCurve from './split';\n\nconst commandTokenRegex = /[MLCSTQAHVZmlcstqahv]|-?[\\d.e+-]+/g;\n/**\n * List of params"
},
{
"path": "src/split.js",
"chars": 4218,
"preview": "/**\n * de Casteljau's algorithm for drawing and splitting bezier curves.\n * Inspired by https://pomax.github.io/bezierin"
},
{
"path": "test/interpolatePath-test.js",
"chars": 223371,
"preview": "/* eslint-disable */\nconst tape = require('tape');\nconst interpolatePath = require('../').interpolatePath;\nconst APPROX_"
},
{
"path": "test/interpolatePathCommands-test.js",
"chars": 8306,
"preview": "/* eslint-disable */\nconst tape = require('tape');\nconst interpolatePathCommands = require('../').interpolatePathCommand"
}
]
About this extraction
This page contains the full source code of the pbeshai/d3-interpolate-path GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 22 files (339.5 KB), approximately 136.5k tokens, and a symbol index with 59 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.