Draw SVG Bezier curve
Asked Answered
M

2

15

I have an array of control points that represent a high-order Bezier curve.
How can I draw this curve using a single SVG-path?

enter image description here

UPD:
For example, I have a set of points: (x1, y1) (x2, y2) (x3, y3) (x4, y4) (x5, y5).
How SVG-path will look like in the terms of C, S, Q or T?

UPD 2: SOLUTION
I asked this question to depict a object path animated with TweenMax.
Later I received reply on GreenSock forum.
Here's CodePen example.

Mixture answered 3/6, 2016 at 14:24 Comment(9)
I love this! Which part are you trying to draw? Are you wanting to animate the stroke as well?Nourish
@ChrisW. no no no, image it's just for clarity. I need to draw a static curve by control pointsMixture
Well, how familiar are you with path data? You'll define your start point with M attribute, and C defines your beginning curve with your points following for the first arc and Z ends it all. You might play with something like this to gain a little familiarity. Or if it's static I often just create those assets in Adobe Illustrator and export to SVG. However without seeing what you've tried, or knowing points etc, this question really is pretty broad.Nourish
C only allows to build a cubic Bezier curve, i.e by 4 control points. I need to build Bezier curve by arbitrary number of control points: 3, 4, 5, 6 and etc.Mixture
Oh sorry, must have buzzed through, thought I saw that deliberate. Then you've got options, you could stick with Cubic and string together seperate lines with S to retain slope. Or better yet, go multiple quadratic with T and even go a step further to define points in Q but there's multiple docs/tutorials out there to explain better than I would have time to in comments here.Nourish
So, I have a set of points, for example (x1, y1) (x2, y2) (x3, y3) (x4, y4) (x5, y5). What will be the final path in the terms of S, T and Q?Mixture
Ya I'm afraid it doesn't quite work that way. From what you just described, of only points, well that's a line. Example of a 5 point polyline might be like <polyline fill="#FFFFFF" stroke="#000000" stroke-miterlimit="10" points="1.704,33.237 35.511,8.239 59.09,63.636 86.647,1.704 98.295,40.624 "/> but then where's your curves? a 5 point curve would be more like <path fill="#FFFFFF" stroke="#000000" stroke-miterlimit="10" d="M1.704,33.237c0,0,23.664-27.76,33.807-24.998 c14.523,3.953,8.64,53.561,23.58,55.396C75.911,65.7,69.734,2.759,86.647,1.704c10.136-0.633,11.648,38.92,11.648,38.92"/>Nourish
Correct me if I am wrong. Let suppose, I have N points. Curve starts at first point and ends at last point. And theres (N-2) more control points. So, theres exists only one bezier curve corresponding these points. No?Mixture
There exists only one (N-1)th order Bezier curve for each set of points with size N, but there are infinitely many Bezier curves that go through those points because any Pth order Bezier curve can be perfectly represented by a (P+1)th order Bezier curve as well. As for the actual post question: SVG can't do arbitrary order curves, only quadratic and cubic, so the real answer is "SVG is not the appropriate web technology here". See answer.Geochronology
R
15

Short answer: you can't.

SVG only has built in Quadratic (2nd order) and Cubic curves (3rd order), whereas the curve you're showing is Quartic (4th order). SVG has no generic "N term Bezier" draw instruction, so you're going to have to compromise here.

Some options:

  1. convert the curve to a (series of) cubic curves and render those instead. This is a pretty hard problem and not really recommended.
  2. sample your curve at enough points such that the polygon through those points looks like a curve at the resolution and zoom level people will be looking at it. This is easy to do, but of course you no longer have a "curve", you now have a polygon.
  3. as above, but fewer points, and then compute the Catmull-Rom curve sequence that goes through those points, then convert those CR curves to cubic Bezier (they're the same type of function, and can be 1:1 transformed from one to the other). This is nicer than 2 because you have a curve, but it might not look quite the same as the original. Of course the more points you use, the better the result will be.
  4. use a canvas to draw your Nth degree Bezier curve, build an image out of the result using the toDataURL function and then load that image as image in your SVG. This will work perfectly fine, but if you use created SVG path styling, making the canvas generate the same style could be a challenge.
  5. this list can get very long, so let's stop here for now.

Bottom line: if you need to show higher order Bezier curves, SVG is not an appropriate technology to use (I'd recommend just doing your animation using the canvas, or better yet, something like d3.js or paper.js. Probably the latter).

And if you end up rolling your own, the sampling function is ludicrously simple. Curves are parametric, controlled by a value t that runs from 0 to 1 (inclusive), and can be written as a nested linear interpolation:

getCurvePoint(t, points) {
  if (points.length === 1) return points[0];
  var newpoints = [];
  for(var i=0,j=1; j<points.length; i++,j++) {
    newpoints[i] = lerp2d(t, points[i], points[j]);
  }
  return getCurvePoint(t,newpoints);
}

with the lerp function being the standard linear interpolation function:

lerp(ratio, start, end) {
  return ratio*start + (1-ratio)*end;
}

lerp2d(ratio, start, end) {
  return {
    x: lerp(ratio, start.x, end.x),
    y: lerp(ratio, start.y, end.y)
  };
}

And a simple jsbin example: http://jsbin.com/pesutibefu/edit?html,js,output using points

var points = [
  {x:50, y:100},
  {x:50, y:250},
  {x:210, y:250},
  {x:250, y:50},
  {x:380, y:150}
];

gives us:

enter image description here

Although a Paper.js sketch would be easier to work with if you need animated pretty paths, with draggable control points, etc.

Reset answered 3/6, 2016 at 17:28 Comment(3)
Thanks, your solution is smart, but I received another interesting answer on GreenSock forum: greensock.com/forums/topic/…Mixture
sure, and that will draw a curve approximation, which I explained as option #1. It does not draw the actual curve. If that's good enough for you, then roll with that. However, getting "the right" answer on a different forum means you either accept the best one you got here, because it's a different forum, or you now post your own answer based on what you were given somewhere else, and then 24h later accept that as the right answer. You don't ask questions on SO just for yourself, other people are going to have this problem as well, and will want to know what the answer is, too =)Geochronology
also, looking at tweenmax, that doesn't solve your problem: it literally draws the curve through the hull points, so now you still need to generate the right on-curve points using the function in my post such that the curvature of the "through" curve lines up with the real curve. And that is far from quick and easy.Geochronology
C
1

SVG does not support Bezier curves other than Quadratic and Cubic. So there is no representation in terms of commands(C, S, Q or T).

A Bezier curve can be defined in two ways: With a "C" command for cubic or with a "Q" command for quadratic. Those command have fixed length of parameters which is 4 for quadratic and 6 for cubic so an example SVG string will look like "Q 150,-300 300,0" you can see an SVG string like that "Q 150,-300 300,0 50,150 100,200" but that is just two quad curves one after another and the number of parameters will always be always multiple of 4 for "Q" and multiple of 6 for "C".

Every command in the list:

M = moveto
L = lineto
H = horizontal lineto
V = vertical lineto
C = curveto
S = smooth curveto
Q = quadratic Bézier curve
T = smooth quadratic Bézier curveto
A = elliptical Arc
Z = closepath

has a representation in JavaScript or in fact in any other standard API (.NET Drawing, Core Graphics, Android Canvas) but in all of them there is no method for anything other than Cubic or Quad. Also in my experience I have never seen a drawing application that has a feature to draw a higher order Bezier curve. So your best option is to simplify the high order curve to a Q or C.

Cranky answered 3/6, 2016 at 17:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.