let points = [{
x: 0,
y: 10
},
{
x: 10,
y: 20
},
{
x: 20,
y: 10
},
{
x: 30,
y: 20
},
{
x: 40,
y: 10
}
];
path.setAttribute("d", smoothQuadratic(points));
function smoothQuadratic(points) {
// set M/starting point
let [Mx, My] = [points[0].x, points[0].y];
let d = `M ${Mx} ${My}`;
renderPoint(svg, [Mx, My], "green", "1");
// split 1st line segment
let [x1, y1] = [points[1].x, points[1].y];
let [xM, yM] = [(Mx + x1) / 2, (My + y1) / 2];
d += `L ${xM} ${yM}`;
renderPoint(svg, [xM, yM], "purple", "1");
for (let i = 1; i < points.length; i += 1) {
let [x, y] = [points[i].x, points[i].y];
// calculate mid point between current and next coordinate
let [xN, yN] = points[i + 1] ? [points[i + 1].x, points[i + 1].y] : [x, y];
let [xM, yM] = [(x + xN) / 2, (y + yN) / 2];
// add quadratic curve:
d += `Q${x} ${y} ${xM} ${yM}`;
renderPoint(svg, [xM, yM], "purple", "1");
renderPoint(svg, [x, y], "green", "1");
}
return d;
}
pathRel.setAttribute("d", smoothQuadraticRelative(points));
function smoothQuadraticRelative(points, skip = 0, decimals = 3) {
let pointsL = points.length;
let even = pointsL - skip - (1 % 2) === 0;
// set M/starting point
let type = "M";
let values = [points[0].x, points[0].y];
let [Mx, My] = values.map((val) => {
return +val.toFixed(decimals);
});
let dRel = `${type}${Mx} ${My}`;
// offsets for relative commands
let xO = Mx;
let yO = My;
// split 1st line segment
let [x1, y1] = [points[1].x, points[1].y];
let [xM, yM] = [(Mx + x1) / 2, (My + y1) / 2];
let [xMR, yMR] = [xM - xO, yM - yO].map((val) => {
return +val.toFixed(decimals);
});
dRel += `l${xMR} ${yMR}`;
xO += xMR;
yO += yMR;
for (let i = 1; i < points.length; i += 1 + skip) {
// control point
let [x, y] = [points[i].x, points[i].y];
let [xR, yR] = [x - xO, y - yO];
// next point
let [xN, yN] = points[i + 1 + skip] ?
[points[i + 1 + skip].x, points[i + 1 + skip].y] :
[points[pointsL - 1].x, points[pointsL - 1].y];
let [xNR, yNR] = [xN - xO, yN - yO];
// mid point
let [xM, yM] = [(x + xN) / 2, (y + yN) / 2];
let [xMR, yMR] = [(xR + xNR) / 2, (yR + yNR) / 2];
type = "q";
values = [xR, yR, xMR, yMR];
// switch to t command
if (i > 1) {
type = "t";
values = [xMR, yMR];
}
dRel += `${type}${values
.map((val) => {
return +val.toFixed(decimals);
})
.join(" ")} `;
xO += xMR;
yO += yMR;
}
// add last line if odd number of segments
if (!even) {
values = [points[pointsL - 1].x - xO, points[pointsL - 1].y - yO];
dRel += `l${values
.map((val) => {
return +val.toFixed(decimals);
})
.join(" ")}`;
}
return dRel;
}
function renderPoint(svg, coords, fill = "red", r = "2") {
let marker =
'<circle cx="' +
coords[0] +
'" cy="' +
coords[1] +
'" r="' +
r +
'" fill="' +
fill +
'" ><title>' +
coords.join(", ") +
"</title></circle>";
svg.insertAdjacentHTML("beforeend", marker);
}
svg {
border: 1px solid #ccc;
width: 45vw;
overflow: visible;
margin-right: 1vw;
}
path {
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
stroke-opacity: 0.5;
}
<svg id="svg" viewBox="0 0 40 30">
<path d="M 0 10 L 10 20 20 10 L 30 20 40 10" fill="none" stroke="#999" stroke-width="1"></path>
<path id="path" d="" fill="none" stroke="red" stroke-width="1" />
</svg>
<svg id="svg2" viewBox="0 0 40 30">
<path d="M 0 10 L 10 20 20 10 L 30 20 40 10" fill="none" stroke="#999" stroke-width="1"></path>
<path id="pathRel" d="" fill="none" stroke="red" stroke-width="1" />
</svg>