Published
Edited
Oct 8, 2020
3 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
parsed = parser.makeAbsolute(parser.parseSVG(d))
Insert cell
Insert cell
Insert cell
// From https://svgwg.org/svg2-draft/implnote.html#ArcConversionEndpointToCenter
function endpointToCenterParameterization(
x1,
y1,
x2,
y2,
largeArcFlag,
sweepFlag,
srx,
sry,
xAxisRotationDeg
) {
const xAxisRotation = degToRad(xAxisRotationDeg);

const cosphi = Math.cos(xAxisRotation);
const sinphi = Math.sin(xAxisRotation);

const [x1p, y1p] = mat2DotVec2(
[cosphi, sinphi, -sinphi, cosphi],
[(x1 - x2) / 2, (y1 - y2) / 2]
);

const [rx, ry] = correctRadii(srx, sry, x1p, y1p);

const sign = largeArcFlag !== sweepFlag ? 1 : -1;
const n = pow(rx) * pow(ry) - pow(rx) * pow(y1p) - pow(ry) * pow(x1p);
const d = pow(rx) * pow(y1p) + pow(ry) * pow(x1p);

const [cxp, cyp] = vec2Scale(
[(rx * y1p) / ry, (-ry * x1p) / rx],
sign * Math.sqrt(Math.abs(n / d))
);

const [cx, cy] = vec2Add(
mat2DotVec2([cosphi, -sinphi, sinphi, cosphi], [cxp, cyp]),
[(x1 + x2) / 2, (y1 + y2) / 2]
);

const a = [(x1p - cxp) / rx, (y1p - cyp) / ry];
const b = [(-x1p - cxp) / rx, (-y1p - cyp) / ry];
const startAngle = vec2Angle([1, 0], a);
const deltaAngle0 = vec2Angle(a, b) % (2 * Math.PI);

const deltaAngle =
!sweepFlag && deltaAngle0 > 0
? deltaAngle0 - 2 * Math.PI
: sweepFlag && deltaAngle0 < 0
? deltaAngle0 + 2 * Math.PI
: deltaAngle0;

const endAngle = startAngle + deltaAngle;

return {
cx,
cy,
rx,
ry,
startAngle,
endAngle,
xAxisRotation,
anticlockwise: deltaAngle < 0
};
}
Insert cell
{
const cmd = parsed[1];
return endpointToCenterParameterization(
cmd.x0,
cmd.y0,
cmd.x,
cmd.y,
cmd.largeArc,
cmd.sweep,
cmd.rx,
cmd.ry,
cmd.xAxisRotation
);
}
Insert cell
function drawCommand(context, cmd) {
switch (cmd.command) {
case "moveto":
context.moveTo(cmd.x, cmd.y);
return;
case "lineto":
case "horizontal lineto":
case "vertical lineto":
context.lineTo(cmd.x, cmd.y);
return;
case "elliptical arc":
const p = endpointToCenterParameterization(
cmd.x0,
cmd.y0,
cmd.x,
cmd.y,
cmd.largeArc,
cmd.sweep,
cmd.rx,
cmd.ry,
cmd.xAxisRotation
);
context.ellipse(
p.cx,
p.cy,
p.rx,
p.ry,
p.xAxisRotation,
p.startAngle,
p.endAngle,
p.anticlockwise
);
return;
case 'closepath':
context.closePath();
return;
default:
console.error('Unsupported command', cmd.command);
}
}
Insert cell
function correctRadii(signedRx, signedRy, x1p, y1p) {
const prx = Math.abs(signedRx);
const pry = Math.abs(signedRy);

const A = pow(x1p) / pow(prx) + pow(y1p) / pow(pry);

const rx = A > 1 ? Math.sqrt(A) * prx : prx;
const ry = A > 1 ? Math.sqrt(A) * pry : pry;

return [rx, ry];
}
Insert cell
function pow(n) {
return Math.pow(n, 2);
}
Insert cell
function mat2DotVec2([m00, m01, m10, m11], [vx, vy]) {
return [m00 * vx + m01 * vy, m10 * vx + m11 * vy];
}
Insert cell
function vec2Add([ux, uy], [vx, vy]) {
return [ux + vx, uy + vy];
}
Insert cell
function vec2Scale([a0, a1], scalar) {
return [a0 * scalar, a1 * scalar];
}
Insert cell
function vec2Dot([ux, uy], [vx, vy]) {
return ux * vx + uy * vy;
}
Insert cell
function vec2Mag([ux, uy]) {
return Math.sqrt(ux ** 2 + uy ** 2);
}
Insert cell
function vec2Angle(u, v) {
const [ux, uy] = u;
const [vx, vy] = v;
const sign = ux * vy - uy * vx >= 0 ? 1 : -1;
return sign * Math.acos(vec2Dot(u, v) / (vec2Mag(u) * vec2Mag(v)));
}
Insert cell
function degToRad(deg) {
return (deg * Math.PI) / 180;
}
Insert cell
parser = require('https://bundle.run/svg-path-parser@1.1.0')
Insert cell
import { slider } from '@jashkenas/inputs'
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more