Home > OS >  SVG partial quadratic curve
SVG partial quadratic curve

Time:12-19

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 sor 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>

  • Related