Bezier
Bezier copied to clipboard
Incorrect curve
Hi,
There is some issue with this code, it doesn't work well with all data. Please replace your points with: "100, 200, 300, 120, 400, 350, 350, 170, 200".
You will see this:
Top of the curve should be exactly in point, not above/below. Do you know how to fix it?
I tried to modify the code for a closed curve and I also have a problem. Couldn't find where it is yet though. I keep digging!
pretty sure this isn't solvable with Bezier curves -- afaik they are an approximation, so they'll never match the data exactly. You can get closer with higher-order curves, but they get computationally very expensive.
It is solvable. The curve doesn't have to be pixel perfect, however as you can see on the screenshot the curve doesn't have turns in the middle of points.
I ended up with this code and it works perfectly fine ;)
(C# code)
public static class BezierCurve
{
public static UIBezierPath GetCurvedPath(CGPoint[] points)
{
var path = new UIBezierPath();
if (points.Length == 0)
{
return path;
}
var p1 = points[0];
path.MoveTo(p1);
if (points.Length == 2)
{
path.AddLineTo(points[1]);
return path;
}
for (int k = 1; k < points.Length; k++)
{
var p2 = points[k];
var midPoint = MidPointFor(p1, p2);
path.AddQuadCurveToPoint(midPoint, ControlPointFor(midPoint, p1));
path.AddQuadCurveToPoint(p2, ControlPointFor(midPoint, p2));
p1 = p2;
}
return path;
}
private static CGPoint MidPointFor(CGPoint p1, CGPoint p2)
{
return new CGPoint((p1.X + p2.X) / 2, (p1.Y + p2.Y) / 2);
}
private static CGPoint ControlPointFor(CGPoint p1, CGPoint p2)
{
var controlPoint = MidPointFor(p1, p2);
var diffY = (nfloat)Math.Abs(p2.Y - controlPoint.Y);
if (p1.Y < p2.Y)
controlPoint.Y += diffY;
else if (p1.Y > p2.Y)
controlPoint.Y -= diffY;
return controlPoint;
}
}
I can confirm, @wojciech-kulik's solution works fine. Here is swift version:
func path() -> UIBezierPath {
...
let linePath = UIBezierPath()
guard graphPoints.count > 0 else { return }
var p1: CGPoint = graphPoints[0]
linePath.move(to: p1)
for i in 1..<graphPoints.count {
let p2 = graphPoints[i]
let midPoint = midPointFor(p1: p1, p2: p2)
linePath.addQuadCurve(to: midPoint, controlPoint: controlPointFor(p1: midPoint, p2: p1))
linePath.addQuadCurve(to: p2, controlPoint: controlPointFor(p1: midPoint, p2: p2))
p1 = p2
}
return linePath
}
private func midPointFor(p1: CGPoint, p2: CGPoint) -> CGPoint {
return CGPoint(x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2)
}
private func controlPointFor(p1: CGPoint , p2: CGPoint ) -> CGPoint {
var controlPoint = midPointFor(p1: p1, p2: p2)
var diffY = abs(p2.y - controlPoint.y)
if (p1.y < p2.y) {
controlPoint.y += diffY
} else if (p1.y > p2.y) {
controlPoint.y -= diffY
}
return controlPoint
}