Charming Path
Charming Path builds SVG d path strings from numbers, handy for <path>, getPointAtLength, or APIs such as layoutTextInPath that expect a path description. Shapes are axis-aligned in user space (no rotation in the path itself). cm.pathContext records paths as command arrays for cm.path.
(() => {
const d = cm.pathCircle(100, 100, 80);
return cm.svg`<svg ${{width: 200, height: 200}}>
<path ${{fill: "none", stroke: "black", stroke_width: 2, d}} />
</svg>`;
})();const d = cm.pathCircle(100, 100, 80);
cm.svg`<svg ${{width: 200, height: 200}}>
<path ${{fill: "none", stroke: "black", stroke_width: 2, d}} />
</svg>`;cm.pathLine(x1, y1, x2, y2)
Returns an open path: M to (x1, y1), L to (x2, y2).
(() => {
const d = cm.pathLine(10, 90, 90, 10);
return cm.svg`<svg ${{width: 100, height: 100, viewBox: "0 0 100 100"}}>
<path ${{fill: "none", stroke: "steelblue", stroke_width: 2, d}} />
</svg>`;
})();const d = cm.pathLine(10, 90, 90, 10);cm.pathCircle(cx, cy, r)
Returns a closed circle centered at (cx, cy) with radius r, using two arc segments.
(() => {
const d = cm.pathCircle(50, 50, 40);
return cm.svg`<svg ${{width: 100, height: 100, viewBox: "0 0 100 100"}}>
<path ${{fill: "none", stroke: "teal", stroke_width: 2, d}} />
</svg>`;
})();const d = cm.pathCircle(100, 100, 50);cm.pathRect(x, y, width, height)
Returns a closed rectangle with top-left (x, y) and size width × height.
(() => {
const d = cm.pathRect(15, 25, 70, 50);
return cm.svg`<svg ${{width: 100, height: 100, viewBox: "0 0 100 100"}}>
<path ${{fill: "none", stroke: "purple", stroke_width: 2, d}} />
</svg>`;
})();const d = cm.pathRect(0, 0, 100, 50);cm.pathEllipse(cx, cy, rx, ry)
Returns a closed axis-aligned ellipse centered at (cx, cy) with radii rx and ry, using two elliptical arcs.
(() => {
const d = cm.pathEllipse(50, 50, 42, 28);
return cm.svg`<svg ${{width: 100, height: 100, viewBox: "0 0 100 100"}}>
<path ${{fill: "none", stroke: "coral", stroke_width: 2, d}} />
</svg>`;
})();const d = cm.pathEllipse(100, 100, 60, 40);cm.pathPolygon(points)
Returns a closed path through points, an array of [x, y] pairs. The first point is moved to with M; later points use L; the path ends with Z.
(() => {
const d = cm.pathPolygon([
[50, 10],
[90, 90],
[10, 90],
]);
return cm.svg`<svg ${{width: 100, height: 100, viewBox: "0 0 100 100"}}>
<path ${{fill: "none", stroke: "darkgreen", stroke_width: 2, d}} />
</svg>`;
})();const d = cm.pathPolygon([
[0, 0],
[100, 0],
[50, 80],
]);cm.pathContext()
Returns a d3-path-compatible context that records path commands as arrays instead of a string. Call the CanvasPathMethods on it (moveTo, lineTo, quadraticCurveTo, bezierCurveTo, arc, arcTo, rect, closePath), then context.toArray() to get the commands. Pass them as the d of cm.path, so a path from d3-shape or d3-geo can be drawn without parsing a string.
const context = cm.pathContext();
context.moveTo(0, 0);
context.lineTo(10, 0);
context.lineTo(10, 10);
context.closePath();
context.toArray(); // [["M", 0, 0], ["L", 10, 0], ["L", 10, 10], ["Z"]]For example, with d3-geo:
const path = d3.geoPath(d3.geoOrthographic());
root.append(cm.path, {
d: () => {
const context = cm.pathContext();
path.context(context)(d3.geoCircle()());
return context.toArray();
},
stroke: "black",
});