Published
Edited
Mar 10, 2022
Importers
Insert cell
# Path d attribute parser
Insert cell
Insert cell
Insert cell
parsedPath = PathLang.PathString.tryParse(dString)
Insert cell
points = pathToPoints(parsedPath)
Insert cell
Path = ADT.Sum([
ADT.Product('path', ['commands']),
ADT.Product('closePath'),
ADT.Product('moveTo', ['x', 'y']),
ADT.Product('moveToDelta', ['dx', 'dy']),
ADT.Product('lineTo', ['x', 'y']),
ADT.Product('lineToDelta', ['dx', 'dy']),
ADT.Product('lineToH', ['x']),
ADT.Product('lineToHDelta', ['dx']),
ADT.Product('lineToV', ['y']),
ADT.Product('lineToVDelta', ['dy']),
ADT.Product('curve', ['controlP1X', 'controlP1Y', 'controlP2X', 'controlP2Y', 'x', 'y']),
ADT.Product('curveDelta', ['controlP1dX', 'controlP1dY', 'controlP2dX', 'controlP2dY', 'dx', 'dy']),
ADT.Product('smoothCurve', ['controlP2X', 'controlP2Y', 'x', 'y']),
ADT.Product('smoothCurveDelta', ['controlP2dX', 'controlP2dY', 'dx', 'dy']),
ADT.Product('quadraticCurve', ['controlPX', 'controlPY', 'x', 'y']),
ADT.Product('quadraticCurveDelta', ['controlPdX', 'controlPdY', 'dx', 'dy']),
ADT.Product('smoothQuadraticCurve', ['x', 'y']),
ADT.Product('smoothQuadraticCurveDelta', ['dx', 'dy']),
ADT.Product('arc', ['xRadius', 'yRadius', 'xAxisRotation', 'largeArcFlag', 'sweepFlag', 'x', 'y']),
ADT.Product('arcDelta', ['xRadius', 'yRadius', 'xAxisRotation', 'largeArcFlag', 'sweepFlag', 'dx', 'dy'])
])
// Path.match({
// path: ({commands}) => (currentPoint) => {
// let points = [];
// commands.forEach(comm => {
// points.concat(path)
// })
// },
// closePath: () => {},
// moveTo: ({x, y}) => {},
// moveToDelta: ({dx, dy}) => {},
// lineTo: ({x, y}) => {},
// lineToDelta: ({dx, dy}) => {},
// lineToH: ({x}) => {},
// lineToHDelta: ({dx}) => {},
// lineToV: ({y}) => {},
// lineToVDelta: ({dy}) => {},
// curve: ({controlP1X, controlP1Y, controlP2X, controlP2Y, x, y}) => {},
// curveDelta: ({controlP1dX, controlP1dY, controlP2dX, controlP2dY, dx, dy}) => {},
// smoothCurve: ({controlP2X, controlP2Y, x, y}) => {},
// smoothCurveDelta: ({controlP2dX, controlP2dY, dx, dy}) => {},
// quadraticCurve: ({controlPX, controlPY, x, y}) => {},
// quadraticCurveDelta: ({controlPdX, controlPdY, dx, dy}) => {},
// smoothQuadraticCurve: ({x, y}) => {},
// smoothQuadraticCurveDelta: ({dx, dy}) => {},
// arc: ({xRadius, yRadius, xAxisRotation, largeArcFlag, sweepFlag, x, y}) => {},
// arcDelta: ({xRadius, yRadius, xAxisRotation, largeArcFlag, sweepFlag, dx, dy}) => {}
// })
Insert cell
PathLang = P.createLanguage({
PathString: r => (
P.alt(
P.seq(
P.alt(
r.MoveTo,
r.MoveToDelta
),
P.seq(
r.Sep.atLeast(0),
r.AnyCommand
).many(),
r.Sep.atLeast(0)
).map(([moveto, rest]) =>
Path.path([moveto, ...rest.map(([sep, comm]) => comm)])),
P.string('')
.map(() => Path.path([]))
)
),
AnyCommand: r => (
P.alt(
r.MoveTo,
r.MoveToDelta,
r.ClosePath,
r.LineTo,
r.LineToDelta,
r.LineToH,
r.LineToHDelta,
r.LineToV,
r.LineToVDelta,
r.CubicCurve,
r.CubicCurveDelta,
r.SmoothCurve,
r.SmoothCurveDelta,
r.QuadraticCurve,
r.QuadraticCurveDelta,
r.SmoothQuadraticCurve,
r.SmoothQuadraticCurveDelta,
r.Arc,
r.ArcDelta
)
),
ClosePath: r => (
P.oneOf("zZ")
.map(Path.closePath)
),
MoveTo: r => (
P.seq(
P.string('M'), r.Sep.times(0, 1),
P.seq(
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1)
).atLeast(1)
).map(([,,coordinates]) => {
const [x,,y] = coordinates[0];
return coordinates.length === 1
? Path.moveTo(x, y)
: Path.path(coordinates.map(([x,,y]) => Path.lineTo(x, y)));
})
),
MoveToDelta: r => (
P.seq(
P.string('m'), r.Sep.times(0, 1),
P.seq(
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1)
).atLeast(1)
).map(([,,coordinates]) => {
const [dx,,dy] = coordinates[0];
return coordinates.length === 1
? Path.moveToDelta(dx, dy)
: Path.path([Path.moveToDelta(dx, dy),
...coordinates.slice(1).map(([dx,,dy]) => Path.lineToDelta(dx, dy))]);
})
),
LineTo: r => (
P.seq(
P.string('L'), r.Sep.times(0, 1),
P.seq(
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1)
).atLeast(1)
).map(([,,coordinates]) => {
const [x,,y] = coordinates[0];

return coordinates.length === 1
? Path.lineTo(x, y)
: Path.path(coordinates.map(([x,,y]) => Path.lineTo(x, y)));
})
),
LineToDelta: r => (
P.seq(
P.string('l'), r.Sep.times(0, 1),
P.seq(
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1)
).atLeast(1)
).map(([,,coordinates]) => {
const [dx,,dy] = coordinates[0];

return coordinates.length === 1
? Path.lineToDelta(dx, dy)
: Path.path(coordinates.map(([dx,,dy]) => Path.lineToDelta(dx, dy)));
})
),
LineToH: r => (
P.seq(
P.string('H'), r.Sep.times(0, 1),
P.seq(
r.Num, r.Sep.times(0, 1)
).atLeast(1)
).map(([,,xs]) => {
const [x] = xs[0];

return xs.length === 1
? Path.lineToH(x)
: Path.path(xs.map(([x]) => Path.lineToH(x)));
})
),
LineToHDelta: r => (
P.seq(
P.string('h'), r.Sep.times(0, 1),
P.seq(
r.Num, r.Sep.times(0, 1)
).atLeast(1)
).map(([,,dxs]) => {
const [dx] = dxs[0];

return dxs.length === 1
? Path.lineToHDelta(dx)
: Path.path(dxs.map(([dx]) => Path.lineToHDelta(dx)));
})
),
LineToV: r => (
P.seq(
P.string('V'), r.Sep.times(0, 1),
P.seq(
r.Num, r.Sep.times(0, 1)
).atLeast(1)
).map(([,,ys]) => {
const [y] = ys[0];

return ys.length === 1
? Path.lineToV(y)
: Path.path(ys.map(([y]) => Path.lineToV(y)));
})
),
LineToVDelta: r => (
P.seq(
P.string('v'), r.Sep.times(0, 1),
P.seq(
r.Num, r.Sep.times(0, 1)
).atLeast(1)
).map(([,,dys]) => {
const [dy] = dys[0];

return dys.length === 1
? Path.lineToVDelta(dy)
: Path.path(dys.map(([dy]) => Path.lineToVDelta(dy)));
})
),
// C x1 y1, x2 y2, x y
// (or)
// c dx1 dy1, dx2 dy2, dx dy
CubicCurve: r => (
P.seq(
P.string('C'), r.Sep.times(0, 1),
P.seq(
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1)
).atLeast(1)
).map(([,,parameterSets]) => {
return Path.path(parameterSets.map(
([controlP1X,,controlP1Y,,controlP2X,,controlP2Y,,x,,y]) => {
return Path.curve(controlP1X, controlP1Y, controlP2X, controlP2Y, x, y);
}
));
})
),
CubicCurveDelta: r => (
P.seq(
P.string('c'), r.Sep.times(0, 1),
P.seq(
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1)
).atLeast(1)
).map(([,,parameterSets]) => {
return Path.path(parameterSets.map(
([controlP1dX,,controlP1dY,,controlP2dX,,controlP2dY,,dx,,dy]) => (
Path.curveDelta(controlP1dX, controlP1dY, controlP2dX, controlP2dY, dx, dy)
)
));
//return Path.curveDelta(controlP1dX, controlP1dY, controlP2dX, controlP2dY, dx, dy);
})
),
// S x2 y2, x y
// (or)
// s dx2 dy2, dx dy
SmoothCurve: r => (
P.seq(
P.string('S'), r.Sep.times(0, 1),
P.seq(
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1)
).atLeast(1)
).map(([,,parameterSets]) => {
const [controlP2X,,controlP2Y,,x,,y] = parameterSets[0];

return parameterSets.length === 1
? Path.smoothCurve(controlP2X, controlP2Y, x, y)
: Path.path(parameterSets.map(([controlP2X,,controlP2Y,,x,,y]) => Path.smoothCurve(controlP2X, controlP2Y, x, y)));
})
),
SmoothCurveDelta: r => (
P.seq(
P.string('s'), r.Sep.times(0, 1),
P.seq(
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1)
).atLeast(1)
).map(([,,parameterSets]) => {
const [controlP2dX,,controlP2dY,,dx,,dy] = parameterSets[0];

return parameterSets.length === 1
? Path.smoothCurveDelta(controlP2dX, controlP2dY, dx, dy)
: Path.path(parameterSets.map(([controlP2dX,,controlP2dY,,dx,,dy]) => Path.smoothCurveDelta(controlP2dX, controlP2dY, dx, dy)));

})
),
QuadraticCurve: r => (
P.seq(
P.string('Q'), r.Sep.times(0, 1),
P.seq(
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1)
).atLeast(1)
).map(([,,parameterSets]) => {
const [controlPX,,controlPY,,x,,y] = parameterSets[0];

return parameterSets.length === 1
? Path.quadraticCurve(controlPX, controlPY, x, y)
: Path.path(parameterSets.map(([controlPX,,controlPY,,x,,y]) => Path.quadraticCurve(controlPX, controlPY, x, y)));
})
),
QuadraticCurveDelta: r => (
P.seq(
P.string('q'), r.Sep.times(0, 1),
P.seq(
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1)
).atLeast(1)
).map(([,,parameterSets]) => {
const [controlPdX,,controlPdY,,dx,,dy] = parameterSets[0];

return parameterSets.length === 1
? Path.quadraticCurveDelta(controlPdX, controlPdY, dx, dy)
: Path.path(parameterSets.map(([controlPdX,,controlPdY,,dx,,dy]) => Path.quadraticCurveDelta(controlPdX, controlPdY, dx, dy)));
})
),
SmoothQuadraticCurve: r => (
P.seq(
P.string('T'), r.Sep.times(0, 1),
P.seq(
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1)
).atLeast(1)
).map(([,,parameterSets]) => {
const [x,,y] = parameterSets[0];

return parameterSets.length === 1
? Path.smoothQuadraticCurve(x, y)
: Path.path(parameterSets.map(([x,,y]) => Path.smoothQuadraticCurve(x, y)))
})
),
SmoothQuadraticCurveDelta: r => (
P.seq(
P.string('t'), r.Sep.times(0, 1),
P.seq(
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1)
).atLeast(1)
).map(([,,parameterSets]) => {
const [dx,,dy] = parameterSets[0];

return parameterSets.length === 1
? Path.smoothQuadraticCurveDelta(dx, dy)
: Path.path(parameterSets.map(([dx,,dy]) => Path.smoothQuadraticCurveDelta(dx, dy)));
})
),
// A rx ry x-axis-rotation large-arc-flag sweep-flag x y
// a rx ry x-axis-rotation large-arc-flag sweep-flag dx dy
// ADT.Product('arc', ['xRadius', 'yRadius', 'xAxisRotation', 'largeArcFlag', 'sweepFlag', 'x', 'y']),
// ADT.Product('arcDelta', ['xRadius', 'yRadius', 'xAxisRotation', 'largeArcFlag', 'sweepFlag', 'dx', 'dy'])
Arc: r => (
P.seq(
P.string('A'), r.Sep.times(0, 1),
P.seq(
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1),
r.Flag, r.Sep.times(0, 1),
r.Flag, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1)
).atLeast(1)
).map(([,,parameterSets]) => {
const [xRadius,,yRadius,,xAxisRotation,,largeArcFlag,,sweepFlag,,x,,y] = parameterSets[0];

return parameterSets.length === 1
? Path.arc(xRadius, yRadius, xAxisRotation, largeArcFlag, sweepFlag, x, y)
: Path.path(parameterSets.map(([xRadius,,yRadius,,xAxisRotation,,largeArcFlag,,sweepFlag,,x,,y]) => {
Path.arc(xRadius, yRadius, xAxisRotation, largeArcFlag, sweepFlag, x, y)
}))
})
),
ArcDelta: r => (
P.seq(
P.string('a'), r.Sep.times(0, 1),
P.seq(
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1),
r.Flag, r.Sep.times(0, 1),
r.Flag, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1),
r.Num, r.Sep.times(0, 1)
).atLeast(1)
).map(([,,parameterSets]) => {
const [xRadius,,yRadius,,xAxisRotation,,largeArcFlag,,sweepFlag,,dx,,dy] = parameterSets[0];

return parameterSets.length === 1
? Path.arcDelta(xRadius, yRadius, xAxisRotation, largeArcFlag, sweepFlag, dx, dy)
: Path.path(parameterSets.map(([xRadius,,yRadius,,xAxisRotation,,largeArcFlag,,sweepFlag,,dx,,dy]) => Path.arcDelta(xRadius, yRadius, xAxisRotation, largeArcFlag, sweepFlag, dx, dy)))
})
),
Num: r => (
// P.regexp(/[+-]?([0-9]*[.])?[0-9]+/)
P.regexp(/((?:-?\d*)\.?\d+[eE][-\+]?\d+)|[+-]?([0-9]*[.])?[0-9]+/)
.map(Number)
),
Sep: r => (
P.regexp(/(\s)|(\s+,\s*)|(\s*,\s+)|(,)/)
),
Flag: r => (
P.oneOf('01')
)
})
Insert cell
pathToPoints = {
const pathToInfo = Path.match({
path: ({commands}) => ({currentPoint, initialPoint, currentSubpath, subpaths}) => {
let interpretInfo = {currentPoint, initialPoint, currentSubpath, subpaths};
commands.forEach(comm => {
interpretInfo = pathToInfo(comm)(interpretInfo);
})
return interpretInfo;
},
closePath: () => ({currentPoint, initialPoint, currentSubpath, subpaths}) => ({
currentPoint: initialPoint,
initialPoint,
currentSubpath: [],
subpaths: [...subpaths, [...currentSubpath, initialPoint]]
}),
moveTo: ({x, y}) => ({currentPoint, initialPoint, currentSubpath, subpaths}) => ({
currentPoint: [x, y],
initialPoint: [x, y],
currentSubpath: [[x, y]],
subpaths: [...subpaths, currentSubpath]
}),
moveToDelta: ({dx, dy}) => ({currentPoint, initialPoint, currentSubpath, subpaths}) => ({
currentPoint: [currentPoint[0] + dx, currentPoint[1] + dy],
initialPoint: [currentPoint[0] + dx, currentPoint[1] + dy],
currentSubpath: [[currentPoint[0] + dx, currentPoint[1] + dy]],
subpaths: [...subpaths, currentSubpath]
}),
lineTo: ({x, y}) => ({currentPoint, initialPoint, currentSubpath, subpaths}) => ({
currentPoint: [x, y],
initialPoint,
currentSubpath: [...currentSubpath, [x, y]],
subpaths
}),
lineToDelta: ({dx, dy}) => ({currentPoint, initialPoint, currentSubpath, subpaths}) => ({
currentPoint: [currentPoint[0] + dx, currentPoint[1] + dy],
initialPoint,
currentSubpath: [...currentSubpath, [currentPoint[0] + dx, currentPoint[1] + dy]],
subpaths
}),
lineToH: ({x}) => ({currentPoint, initialPoint, currentSubpath, subpaths}) => ({
currentPoint: [x, currentPoint[1]],
initialPoint,
currentSubpath: [...currentSubpath, [x, currentPoint[1]]],
subpaths
}),
lineToHDelta: ({dx}) => ({currentPoint, initialPoint, currentSubpath, subpaths}) => ({
currentPoint: [currentPoint[0] + dx, currentPoint[1]],
initialPoint,
currentSubpath: [...currentSubpath, [currentPoint[0] + dx, currentPoint[1]]],
subpaths
}),
lineToV: ({y}) => ({currentPoint, initialPoint, currentSubpath, subpaths}) => ({
currentPoint: [currentPoint[0], y],
initialPoint,
currentSubpath: [...currentSubpath, [currentPoint[0], y]],
subpaths
}),
lineToVDelta: ({dy}) => ({currentPoint, initialPoint, currentSubpath, subpaths}) => ({
currentPoint: [currentPoint[0], currentPoint[1] + dy],
initialPoint,
currentSubpath: [...currentSubpath, [currentPoint[0], currentPoint[1] + dy]],
subpaths
}),
// TODO
// y=u0(1−x)3+3u1(1−x)2x+3u2(1−x)x2+u3x3
// https://math.stackexchange.com/questions/26846/is-there-an-explicit-form-for-cubic-b%C3%A9zier-curves
curve: ({controlP1X, controlP1Y, controlP2X, controlP2Y, x, y}) =>
({currentPoint, initialPoint, currentSubpath, subpaths}) => {
const bezier = cubicBezier(currentPoint, [controlP1X, controlP1Y], [controlP2X, controlP2Y], [x, y]);
return {
currentPoint: [x, y],
initialPoint,
currentSubpath: [
...currentSubpath,
...Array.from({length: 10})
.map((_, i) => bezier(i / 10)),
[x, y]
],
subpaths
}
},
curveDelta: ({controlP1dX, controlP1dY, controlP2dX, controlP2dY, dx, dy}) =>
({currentPoint, initialPoint, currentSubpath, subpaths}) => {
const bezier = cubicBezier(currentPoint,
[currentPoint[0] + controlP1dX, currentPoint[1] + controlP1dY],
[currentPoint[0] + controlP2dX, currentPoint[1] + controlP2dY],
[currentPoint[0] + dx, currentPoint[1] + dy]);

return {
currentPoint: [currentPoint[0] + dx, currentPoint[1] + dy],
initialPoint,
currentSubpath: [
...currentSubpath,
...Array.from({length: 10})
.map((_, i) => bezier(i / 10)),
[currentPoint[0] + dx, currentPoint[1] + dy]
],
subpaths
};
},
smoothCurve: ({controlP2X, controlP2Y, x, y}) =>
({currentPoint, initialPoint, currentSubpath, subpaths}) => ({
currentPoint: [x, y],
initialPoint,
currentSubpath: [...currentSubpath, [x, y]],
subpaths
}),
smoothCurveDelta: ({controlP2dX, controlP2dY, dx, dy}) =>
({currentPoint, initialPoint, currentSubpath, subpaths}) => ({
currentPoint: [currentPoint[0] + dx, currentPoint[1] + dy],
initialPoint,
currentSubpath: [...currentSubpath, [currentPoint[0] + dx, currentPoint[1] + dy]],
subpaths
}),
quadraticCurve: ({controlPX, controlPY, x, y}) =>
({currentPoint, initialPoint, currentSubpath, subpaths}) => ({
currentPoint: [x, y],
initialPoint,
currentSubpath: [...currentSubpath, [x, y]],
subpaths
}),
quadraticCurveDelta: ({controlPdX, controlPdY, dx, dy}) =>
({currentPoint, initialPoint, currentSubpath, subpaths}) => ({
currentPoint: [currentPoint[0] + dx, currentPoint[1] + dy],
initialPoint,
currentSubpath: [...currentSubpath, [currentPoint[0] + dx, currentPoint[1] + dy]],
subpaths
}),
smoothQuadraticCurve: ({x, y}) => ({currentPoint, initialPoint, currentSubpath, subpaths}) => ({
currentPoint: [x, y],
initialPoint,
currentSubpath: [...currentSubpath, [x, y]],
subpaths
}),
smoothQuadraticCurveDelta: ({dx, dy}) => ({currentPoint, initialPoint, currentSubpath, subpaths}) => ({
currentPoint: [currentPoint[0] + dx, currentPoint[1] + dy],
initialPoint,
currentSubpath: [...currentSubpath, [currentPoint[0] + dx, currentPoint[1] + dy]],
subpaths
}),
arc: ({xRadius, yRadius, xAxisRotation, largeArcFlag, sweepFlag, x, y}) =>
({currentPoint, initialPoint, currentSubpath, subpaths}) => {
const c = [(x + currentPoint[0]) / 2,
(y + currentPoint[1]) / 2];

const pointAtAngle = (angle) => {
// φ is the angle from the x-axis of the current coordinate system to the x-axis of the ellipse.
const φ = xAxisRotation;

const x = Math.cos(φ) * xRadius * Math.cos(angle) - Math.sin(φ) * yRadius * Math.sin(angle) + c[0];
const y = Math.sin(φ) * xRadius * Math.cos(angle) + Math.cos(φ) * yRadius * Math.sin(angle) + c[1];
return [x, y];
};

const startAngle = Math.atan2(currentPoint[1] - c[1],
currentPoint[0] - c[0]);
const endAngle = Math.atan2(y - c[1],
x - c[0]);

const numPoints = 10;
const arcPoints = Array.from({length: numPoints})
.map((_, i) => {
const angle = (i + 1) / (numPoints + 1) * (endAngle - startAngle) + startAngle;
return pointAtAngle(angle);
});

return {
currentPoint: [x, y],
initialPoint,
currentSubpath: [...currentSubpath, ...arcPoints, [x, y]],
subpaths
};
},
// ({
// currentPoint: [x, y],
// initialPoint,
// currentSubpath: [...currentSubpath, [x, y]],
// subpaths
// }),
arcDelta: ({xRadius, yRadius, xAxisRotation, largeArcFlag, sweepFlag, dx, dy}) =>
({currentPoint, initialPoint, currentSubpath, subpaths}) => ({
currentPoint: [currentPoint[0] + dx, currentPoint[1] + dy],
initialPoint,
currentSubpath: [...currentSubpath, [currentPoint[0] + dx, currentPoint[1] + dy]],
subpaths
})
});

const pathToPoints = (path) => {
const {currentSubpath, subpaths} = pathToInfo(path)({
currentPoint: [0, 0],
initialPoint: null,
currentSubpath: [],
subpaths: []
});

const allSubpaths = [...subpaths, currentSubpath]
// Remove empty subpaths
.filter(subpath => !(subpath.length <= 1))
// Remove any strings of duplicate points
.map(subpath => {
const {subpathWithoutDupes} = subpath.reduce(({subpathWithoutDupes, lastPoint}, p) => {
if (p[0] === lastPoint[0] && p[1] === lastPoint[1]) {
return {subpathWithoutDupes, lastPoint};
}
subpathWithoutDupes.push(p);
return {subpathWithoutDupes, lastPoint: p};
}, {subpathWithoutDupes: [], lastPoint: [null, null]});
return subpathWithoutDupes;
});
return allSubpaths;
};
return pathToPoints;
}
Insert cell
dStringToPoints = (str) => {;
const parsed = PathLang.PathString.tryParse(str);
return pathToPoints(parsed);
}
Insert cell
dStringToPoints('M 0 0c-1.8624 0-19.353 17.895-21.377 21.944-0.9717 1.9434-2.4292 4.1297-3.158 4.8585-0.7288 0.6478-2.3483 4.3726-3.6439 8.1785l-2.2673 6.8829-12.308-0.40487')
Insert cell
dStringToPoints('M 10 10 L 100 100 Z')
Insert cell
dStringToPoints('')
Insert cell
dStringToPoints('m1664 250.89c-12.794 1.4576-29.961 4.1297-30.366 4.6966')
Insert cell
dStringToPoints("m 200.09994,74.519978 c -0.53153,1.144991 -1.3964,2.061311 -2.33538,2.866153 -0.20983,0.179854 -0.30082,0.483624 -0.53077,0.636923 -0.12139,0.08093 -0.29052,0.04868 -0.42462,0.106154 -0.21638,0.09273 -0.35363,0.424615 -0.53077,0.424615")
Insert cell
dStringToPoints('m 200.09994,74.519978 c -0.53153,1.144991 -1.3964,2.061311 -2.33538,2.866153 -0.20983,0.179854 -0.30082,0.483624 -0.53077,0.636923 -0.12139,0.08093 -0.29052,0.04868 -0.42462,0.106154 -0.21638,0.09273 -0.35363,0.424615 -0.53077,0.424615')
Insert cell
dStringToPoints('m 542.1,199.4 c -5.5e-4,0.43867 -0.0458,0.87957 -0.002,1.31601 0.008,0.082 0.28909,0.69483 0.30025,0.75062 0.18949,0.94746 -0.23218,1.82945 -0.45038,2.70223 -0.17371,0.69486 0.18805,0.26233 -0.15012,0.6005 -0.49482,1.06682 -1.03379,1.98469 -1.20063,3.15261 -0.0212,0.14861 0.0527,0.3098 0,0.45037 -0.29467,0.78577 -0.88789,1.46909 -1.201,2.25186 -0.11754,0.29386 -0.10254,0.65361 -0.30025,0.90075 -0.2801,0.35012 -0.60049,0.24347 -0.60049,0.75062')
Insert cell
dStringToPoints('m 397,370.2 c -0.22407,0.30231 -0.47552,0.58613 -0.67221,0.90693 -0.18861,0.30764 0.41484,-0.59131 0.6005,-0.90074 1.089,-1.815 3.35193,-2.71575 4.67171,-4.40619 -0.41451,0.95501 -2.4815,0.71473 -3.17047,1.40371 -0.15824,0.15824 -0.12121,0.46622 -0.30025,0.60049 -1.30616,0.97962 -1.88659,-0.0368 -1.12928,2.3958 Z')
Insert cell
cubicBezier = (startPoint, controlPoint1, controlPoint2, endPoint) => (t) => {
const x = Math.pow(1 - t, 3) * startPoint[0] +
3 * Math.pow(1 - t, 2) * t * controlPoint1[0] +
3 * (1 - t) * t * t * controlPoint2[0] +
Math.pow(t, 3) * endPoint[0];
const y = Math.pow(1 - t, 3) * startPoint[1] +
3 * Math.pow(1 - t, 2) * t * controlPoint1[1] +
3 * (1 - t) * t * t * controlPoint2[1] +
Math.pow(t, 3) * endPoint[1];
return [x, y];
}
// X(t) = (1-t)^3 * X0 + 3*(1-t)^2 * t * X1 + 3*(1-t) * t^2 * X2 + t^3 * X3
//Y(t) = (1-t)^3 * Y0 + 3*(1-t)^2 * t * Y1 + 3*(1-t) * t^2 * Y2 + t^3 * Y3
// https://stackoverflow.com/questions/8217346/cubic-bezier-curves-get-y-for-given-x
Insert cell
last = arr => arr[arr.length - 1]
Insert cell
import {ADT} from '@tstodter/tagged-types'
Insert cell
Parsimmon = require('parsimmon@1.18.1/build/parsimmon.umd.min.js')
Insert cell
P = Parsimmon
Insert cell
svg`<svg width="150" height="150" viewBox="0 0 30 30">
<path d="M 0 0 A 20 5 2 0 1 7 10" stroke="black" stroke-width="0.2px" fill="none"></path>

${dStringToPoints('M 0 0 A 20 5 2 0 1 7 10')[0].map(p => svg`
<circle cx="${p[0]}" cy="${p[1]}" r="0.3" fill="red"></circle>
`)}
</svg>`
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more