I have an SVG path defined using a quadratic path:
M x11 y11 Q cx cy x12 y12
This intersects with another line:
M x21 y21 L x22 y22
I am using a library which determines the intersection point of two SVG paths, xintersection yintersection. What I want to do now is draw the quadratic path only up to the intersection point, but following the same curve as though the full path were being drawn, i.e.:
M x11 y11 Q cxModified cyModified xintersection yintersection
How do I determine what the new control point, cxModified cyModified, should be?
CodePudding user response:
As commented by @ccprog bezier.js can do this.
You need to convert your svg <path>
elements to bezier.js objects like so:
let line = {
p1: {x:100, y:20},
p2: {x:99, y:180}
};
let curve = new Bezier.fromSVG(curveEl.getAttribute('d'));
This won't work for horizontal or vertical lines, you need to give them a slight x or y slant.
Bezier.fromSVG()
can convert yout <path>
d
attribute to a bezier curve object (quadratic or cubic) - but it expects absolute commands. Shorthand commands like s
or t
won't be normalized automatically.
Then you can retrieve intersection t
values (0-1) via intersects()
method and call split(t)
to get the curve command coordinates for left and right segment:
let intersections = curve.intersects(line);
let splitSegments = curve.split(intersections);
let left = splitSegments.left.points;
let right = splitSegments.right.points;
let line = {
p1: {x:100, y:20},
p2: {x:99, y:180}
};
let curve = new Bezier.fromSVG(curve1.getAttribute('d'));
// get intersection t value 0-1
let intersectT = curve.intersects(line);
let splitSegments = curve.split(intersectT);
// render
let dLeft = pointsToD(splitSegments.left.points);
left.setAttribute("d", dLeft);
let dRight = pointsToD(splitSegments.right.points);
right.setAttribute("d", dRight);
/**
* convert intersection result to
* svg d attribute
*/
function pointsToD(points) {
let d = `M ${points[0].x} ${points[0].y} `;
// cubic or quadratic bezier
let command = points.length > 3 ? "C" : "Q";
points.shift();
d =
command
points
.map((val) => {
return `${val.x} ${val.y}`;
})
.join(" ");
return d;
}
svg{
width:20em;
border:1px solid #ccc;
}
path{
fill:none;
}
<svg id="svg" viewBox="0 0 200 200">
<path id="line1" d="M 100 20 L 99 180" fill="none" stroke="#ccc" stroke-width="5" />
<path id="curve1" d="M 48 84 Q 100 187 166 37" fill="none" stroke="#ccc" stroke-width="5" />
<!-- result -->
<path id="left" d="" fill="none" stroke="green" stroke-width="2" />
<path id="right" d="" fill="none" stroke="red" stroke-width="2" />
</svg>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/bezier.js"></script>