Public
Edited
Dec 11, 2023
1 fork
Importers
9 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
parse = d =>
d
.trim()
.split(/(?=[MmLlHhVvCcSsAaQqTtZz])/)
.map(d => ({
type: d.charAt(0),
values: replaceRecursive(d.substr(1), /([0-9]*\.[0-9]*)\./, "$1 .")
.replace(/([0-9.])-/g, "$1 -")
.split(/[\n\s,]/)
.filter(d => d.length)
.map(d => +d)
}))
Insert cell
parse(test)
Insert cell
Insert cell
stringify = pathObject =>
pathObject.map(d => d.type + d.values.join(" ")).join("")
Insert cell
Insert cell
stringify(parse(test))
Insert cell
Insert cell
getBBox = pathString => getBBoxObj(parse(pathString))
Insert cell
getBBoxObj = pathObject => {
if (pathObject.some(isRelative)) pathObject = toAbsoluteObj(pathObject);

const x = [].concat(...pathObject.map(getX));
const x0 = Math.min(...x);
const x1 = Math.max(...x);

const y = [].concat(...pathObject.map(getY));
const y0 = Math.min(...y);
const y1 = Math.max(...y);

return {
x: x0,
y: y0,
width: x1 - x0,
height: y1 - y0
};
}
Insert cell
getBBox(test)
Insert cell
Insert cell
Insert cell
center = pathString => stringify(centerObj(parse(pathString)))
Insert cell
centerObj = pathObject => {
if (pathObject.some(isRelative)) pathObject = toAbsoluteObj(pathObject);
const bb = getBBoxObj(pathObject);
return translateObj(-(2 * bb.x + bb.width) / 2, -(2 * bb.y + bb.height) / 2)(
pathObject
);
}
Insert cell
Insert cell
Insert cell
translate = (x, y) => pathString =>
stringify(translateObj(x, y)(parse(pathString)))
Insert cell
translateObj = (x, y) => pathObject =>
toAbsoluteObj(pathObject).map(({ type, values }) => ({
type,
values: applyXY(d => d + x, d => d + y)({ type, values })
}))
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
scale = (x, y) => pathString => stringify(scaleObj(x, y)(parse(pathString)))
Insert cell
scaleObj = (x, y) => {
if (!y) y = x;
if (typeof x === "number") {
let _x = x;
x = d => d * _x;
}
if (typeof y === "number") {
let _y = y;
y = d => d * _y;
}
return pathObject =>
pathObject.map(({ type, values }) => ({
type,
values: applyXY(x, y, x, y)({ type, values })
}));
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
showTest(
scale(d => 115 + 115 * Math.sin(d / (300 * Math.sin(now / 1000))), d => d)
)
Insert cell
Insert cell
size = (w, h) => pathString => stringify(sizeObj(w, h)(parse(pathString)))
Insert cell
sizeObj = (w = Infinity, h = Infinity) => pathObject => {
const bb = getBBoxObj(pathObject);
const ratio = Math.min(w / bb.width, h / bb.height);
return scaleObj(ratio)(pathObject);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
combine = arr =>
arr
.map(parse)
.map(path => {
if (path[0].type === "m" && path[0].values.length > 2) {
return [
{
type: "M",
values: path[0].values.slice(0, 2)
},
{
type: "l",
values: path[0].values.slice(2)
},
...path.slice(1)
];
} else if (path[0].type === "m") {
return [
{
type: "M",
values: path[0].values
},
...path.slice(1)
];
}
return path;
})
.map(stringify)
.join("")
Insert cell
Insert cell
Insert cell
toAbsolute = pathString => stringify(toAbsoluteObj(parse(pathString)))
Insert cell
// TODO: this should probably just be a loop, not recursive…
function toAbsoluteObj(pathObject, last = [0, 0], initial = [0, 0]) {
if (!pathObject.length) return [];
const [first, ...rest] = normalizeImplicitCommandsObj(pathObject);
const newFirst = {
type: first.type.toUpperCase(),
values: isRelative(first)
? applyXY(x => x + last[0], y => y + last[1])(first)
: first.values
};
last = getEndPoint(newFirst, last, initial);
if (newFirst.type === "M") initial = last;
return [newFirst, ...toAbsoluteObj(rest, last, initial)];
}
Insert cell
Insert cell
Insert cell
compress = precision => pathString => stringify(compressObj(precision)(parse(pathString)))
Insert cell
compressObj = precision => pathObject =>
pathObject.map(({ type, values }) => ({
type,
values: values.map(d => +d.toFixed(precision))
}))
Insert cell
Insert cell
normalizeImplicitCommands = pathString =>
stringify(normalizeImplicitCommandsObj(parse(pathString)))
Insert cell
normalizeImplicitCommandsObj = pathObject =>
[].concat(
...pathObject.map(({ type, values }) => {
if (!values.length) return { type, values };
const chunks = [];
for (let i = 0; i < values.length; i += paramLengths[type]) {
chunks.push(values.slice(i, i + paramLengths[type]));
}
return chunks.map((chunk, i) => {
let newType = type === "M" ? "L" : type === "m" ? "l" : type;
return { type: i ? newType : type, values: chunk };
});
})
)
Insert cell
Insert cell
Insert cell
normalizeHV = pathString => stringify(normalizeHVObj(parse(pathString)))
Insert cell
// TODO: rewrite; not as reliable as toAbsolute right now, I think it chokes on Z
function normalizeHVObj(pathObject, initial = [0, 0]) {
if (!pathObject.length) return [];
const [{ type, values }, ...rest] = pathObject;
let newValues;
switch (type) {
case "H":
newValues = [values[0], initial[1]];
return [
{
type: "L",
values: newValues
},
...normalizeHVObj(rest, newValues)
];
case "V":
newValues = [initial[0], values[0]];
return [
{
type: "L",
values: newValues
},
...normalizeHVObj(rest, newValues)
];
default:
return [
{ type, values },
...normalizeHVObj(rest, type === "C" ? values.slice(4) : values)
];
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// require('https://bundle.run/path-data-polyfill@1.0.3')
Insert cell
// pathDataPolyfill = pathString =>
// svg`<svg><path d="${pathString}"/></svg>`
// .querySelector("path")
// .getPathData({ normalize: true })
Insert cell
// {
// const node = svg`<svg height="300"><path stroke="black" fill="none" transform="translate(0, -832)" /></svg>`;
// const path = node.querySelector("path");
// path.setPathData(pathDataPolyfill(test));
// const newPath = translate(0, -852.36216)(path.getAttribute("d"));
// return newPath;
// return svg`<svg height="300"><path d="${newPath}" stroke="black" fill="none" /></svg>`;
// }
Insert cell
Insert cell
// svgPathParser = require('https://bundle.run/svg-path-parser@1.1.0')
Insert cell
// svgPathParser.makeAbsolute(svgPathParser(test))
Insert cell
Insert cell
// getTransformToElement = elem =>
// elem
// .getScreenCTM()
// .inverse()
// .multiply(this.getScreenCTM())
Insert cell
Insert cell
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