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

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