# Draw Graph curves with UIBezierPath

I’m drawing a graph in my application. My problem is that I want to draw line joining vertex points as curves. Currently I’m drawing them with `UIBezierPath`‘s function `addLineToPoint:`. I want to draw them as curves. I’m well aware that `UIBezierPath` has following two functions to support this feature.

Cubic curve:`addCurveToPoint:controlPoint1:controlPoint2:`
Quadratic curve:`addQuadCurveToPoint:controlPoint:`

• Draw ellipse with start and end angle in Objective-C
• iOS: UIBezierPath and CAShapeLayer fillRule
• Draw a path with variable width in iOS
• Stretchy UIBezierPath line?
• Place images along a bezier path
• But problem is that I don’t have control points. All i have is two end points. Neither I found a method/formula to determine control points. Can anyone help me here? I will appreciate if someone can suggest some alternative…

### 8 Solutions Collect From Internet About “Draw Graph curves with UIBezierPath”

Expand on Abid Hussain’s answer on Dec 5 ’12.

i implemeted the code, and it worked but result looked like that:

With little adjustment, i was able to get what i wanted:

What i did was addQuadCurveToPoint to mid points as well, using mid-mid points as control points. Below is the code based on Abid’s sample; hope it helps someone.

``````+ (UIBezierPath *)quadCurvedPathWithPoints:(NSArray *)points
{
UIBezierPath *path = [UIBezierPath bezierPath];

NSValue *value = points[0];
CGPoint p1 = [value CGPointValue];
[path moveToPoint:p1];

if (points.count == 2) {
value = points[1];
CGPoint p2 = [value CGPointValue];
return path;
}

for (NSUInteger i = 1; i < points.count; i++) {
value = points[i];
CGPoint p2 = [value CGPointValue];

CGPoint midPoint = midPointForPoints(p1, p2);

p1 = p2;
}
return path;
}

static CGPoint midPointForPoints(CGPoint p1, CGPoint p2) {
return CGPointMake((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
}

static CGPoint controlPointForPoints(CGPoint p1, CGPoint p2) {
CGPoint controlPoint = midPointForPoints(p1, p2);
CGFloat 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;
}
``````

If you only have two points then you cannot really extrapolate them out into a curve.

If you have more than two points (i.e. several points along a curve) then you can roughly draw a curve to follow them.

If you do the following…

OK, say you have 5 points. p1, p2, p3, p4, and p5.
You need to work out the midpoint between each pair of points.
m1 = midpoint of p1 and p2 (and so on)…

So you have m1, m2, m3, and m4.

Now you can use the mid points as the end points of the sections of the curve and the points as the control points for a quad curve…

So…

Move to point m1.
Add quad curve to point m2 with p2 as a control point.

Add quad curve to point m3 with p3 as a control point.

Add quad curve to point m4 with p4 as a control point.

and so on…

This will get you all apart from the ends of the curve (can’t remember how to get them at the moment, sorry).

SO I found a work around based on @Fogmeister’s answer.

``````    UIBezierPath *path = [UIBezierPath bezierPath];
[path setLineWidth:3.0];
[path setLineCapStyle:kCGLineCapRound];
[path setLineJoinStyle:kCGLineJoinRound];

// actualPoints are my points array stored as NSValue

NSValue *value = [actualPoints objectAtIndex:0];
CGPoint p1 = [value CGPointValue];
[path moveToPoint:p1];

for (int k=1; k<[actualPoints count];k++) {

NSValue *value = [actualPoints objectAtIndex:k];
CGPoint p2 = [value CGPointValue];

CGPoint centerPoint = CGPointMake((p1.x+p2.x)/2, (p1.y+p2.y)/2);

// See if your curve is decreasing or increasing
// You can optimize it further by finding point on normal of line passing through midpoint

if (p1.y<p2.y) {
centerPoint = CGPointMake(centerPoint.x, centerPoint.y+(abs(p2.y-centerPoint.y)));
}else if(p1.y>p2.y){
centerPoint = CGPointMake(centerPoint.x, centerPoint.y-(abs(p2.y-centerPoint.y)));
}

p1 = p2;
}

[path stroke];
``````

Using @user1244109’s answer I implement a better algorithm in Swift 3.

``````var data: [CGFloat] = [0, 0, 0, 0, 0, 0] {
didSet {
setNeedsDisplay()
}
}

func coordXFor(index: Int) -> CGFloat {
return bounds.height - bounds.height * data[index] / (data.max() ?? 0)
}

override func draw(_ rect: CGRect) {

UIColor.black.setStroke()
path.lineWidth = 1
path.stroke()
}

let path = UIBezierPath()
let step = bounds.width / CGFloat(data.count - 1)

var p1 = CGPoint(x: 0, y: coordXFor(index: 0))
path.move(to: p1)

drawPoint(point: p1, color: UIColor.red, radius: 3)

if (data.count == 2) {
path.addLine(to: CGPoint(x: step, y: coordXFor(index: 1)))
return path
}

var oldControlP: CGPoint?

for i in 1..<data.count {
let p2 = CGPoint(x: step * CGFloat(i), y: coordXFor(index: i))
drawPoint(point: p2, color: UIColor.red, radius: 3)
var p3: CGPoint?
if i == data.count - 1 {
p3 = nil
} else {
p3 = CGPoint(x: step * CGFloat(i + 1), y: coordXFor(index: i + 1))
}

let newControlP = controlPointForPoints(p1: p1, p2: p2, p3: p3)

path.addCurve(to: p2, controlPoint1: oldControlP ?? p1, controlPoint2: newControlP ?? p2)

p1 = p2
oldControlP = imaginFor(point1: newControlP, center: p2)
}
return path;
}

func imaginFor(point1: CGPoint?, center: CGPoint?) -> CGPoint? {
guard let p1 = point1, let center = center else {
return nil
}
let newX = 2 * center.x - p1.x
let diffY = abs(p1.y - center.y)
let newY = center.y + diffY * (p1.y < center.y ? 1 : -1)

return CGPoint(x: newX, y: newY)
}

func midPointForPoints(p1: CGPoint, p2: CGPoint) -> CGPoint {
return CGPoint(x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2);
}

func controlPointForPoints(p1: CGPoint, p2: CGPoint, p3: CGPoint?) -> CGPoint? {
guard let p3 = p3 else {
return nil
}

let leftMidPoint  = midPointForPoints(p1: p1, p2: p2)
let rightMidPoint = midPointForPoints(p1: p2, p2: p3)

var controlPoint = midPointForPoints(p1: leftMidPoint, p2: imaginFor(point1: rightMidPoint, center: p2)!)

// this part needs for optimization

if p1.y < p2.y {
if controlPoint.y < p1.y {
controlPoint.y = p1.y
}
if controlPoint.y > p2.y {
controlPoint.y = p2.y
}
} else {
if controlPoint.y > p1.y {
controlPoint.y = p1.y
}
if controlPoint.y < p2.y {
controlPoint.y = p2.y
}
}

let imaginContol = imaginFor(point1: controlPoint, center: p2)!
if p2.y < p3.y {
if imaginContol.y < p2.y {
controlPoint.y = p2.y
}
if imaginContol.y > p3.y {
let diffY = abs(p2.y - p3.y)
controlPoint.y = p2.y + diffY * (p3.y < p2.y ? 1 : -1)
}
} else {
if imaginContol.y > p2.y {
controlPoint.y = p2.y
}
if imaginContol.y < p3.y {
let diffY = abs(p2.y - p3.y)
controlPoint.y = p2.y + diffY * (p3.y < p2.y ? 1 : -1)
}
}

return controlPoint
}

func drawPoint(point: CGPoint, color: UIColor, radius: CGFloat) {
color.setFill()
ovalPath.fill()
}
``````

I’ve done a bit of work on this and get pretty good results from the Catmull-Rom spline. Here is a method that takes an array of CGPoints (stored as NSValues) and adds the line to the given view.

``````- (void)addBezierPathBetweenPoints:(NSArray *)points
toView:(UIView *)view
withColor:(UIColor *)color
andStrokeWidth:(NSUInteger)strokeWidth
{
UIBezierPath *path = [UIBezierPath bezierPath];

float granularity = 100;

[path moveToPoint:[[points firstObject] CGPointValue]];

for (int index = 1; index < points.count - 2 ; index++) {

CGPoint point0 = [[points objectAtIndex:index - 1] CGPointValue];
CGPoint point1 = [[points objectAtIndex:index] CGPointValue];
CGPoint point2 = [[points objectAtIndex:index + 1] CGPointValue];
CGPoint point3 = [[points objectAtIndex:index + 2] CGPointValue];

for (int i = 1; i < granularity ; i++) {
float t = (float) i * (1.0f / (float) granularity);
float tt = t * t;
float ttt = tt * t;

CGPoint pi;
pi.x = 0.5 * (2*point1.x+(point2.x-point0.x)*t + (2*point0.x-5*point1.x+4*point2.x-point3.x)*tt + (3*point1.x-point0.x-3*point2.x+point3.x)*ttt);
pi.y = 0.5 * (2*point1.y+(point2.y-point0.y)*t + (2*point0.y-5*point1.y+4*point2.y-point3.y)*tt + (3*point1.y-point0.y-3*point2.y+point3.y)*ttt);

if (pi.y > view.frame.size.height) {
pi.y = view.frame.size.height;
}
else if (pi.y < 0){
pi.y = 0;
}

if (pi.x > point0.x) {
}
}

}

[path addLineToPoint:[[points objectAtIndex:[points count] - 1] CGPointValue]];

CAShapeLayer *shapeView = [[CAShapeLayer alloc] init];

shapeView.path = [path CGPath];

shapeView.strokeColor = color.CGColor;
shapeView.fillColor = [UIColor clearColor].CGColor;
shapeView.lineWidth = strokeWidth;
[shapeView setLineCap:kCALineCapRound];

}
``````

I use it here https://github.com/johnyorke/JYGraphViewController

A good solution would be to create a straight `UIBezierPath` through your points and then use a spline to curve the line. Check out this other answer which gives a category for UIBezierPath that performs a Catmull-Rom Spline. Drawing Smooth Curves – Methods Needed

here’s @user1244109 ‘s answer converted to Swift 3

``````private func quadCurvedPath(with points:[CGPoint]) -> UIBezierPath {
let path = UIBezierPath()
var p1 = points[0]

path.move(to: p1)

if points.count == 2 {
return path
}

for i in 0..<points.count {
let mid = midPoint(for: (p1, points[i]))
controlPoint: controlPoint(for: (mid, p1)))
controlPoint: controlPoint(for: (mid, points[i])))
p1 = points[i]
}
return path
}

private func midPoint(for points: (CGPoint, CGPoint)) -> CGPoint {
return CGPoint(x: (points.0.x + points.1.x) / 2 , y: (points.0.y + points.1.y) / 2)
}

private func controlPoint(for points: (CGPoint, CGPoint)) -> CGPoint {
var controlPoint = midPoint(for: points)
let diffY = abs(points.1.y - controlPoint.y)

if points.0.y < points.1.y {
controlPoint.y += diffY
} else if points.0.y > points.1.y {
controlPoint.y -= diffY
}
return controlPoint
}
``````

While analyzing the code from @roman-filippov, I simplified the code some. Here’s a complete Swift playground version, and an ObjC version below that.

I found that if x increments are not regular, then the original algorithm creates some unfortunate retrograde lines when points are close together on the x axis. Simply constraining the control points to not exceed the following x-value seems to fix the problem, although I have no mathematical justification for this, just experimentation. There are two sections marked as //**added` which implement this change.

``````import UIKit

infix operator °
func °(x: CGFloat, y: CGFloat) -> CGPoint {
return CGPoint(x: x, y: y)
}

extension UIBezierPath {
func drawPoint(point: CGPoint, color: UIColor, radius: CGFloat) {
color.setFill()
ovalPath.fill()
}

func drawWithLine (point: CGPoint, color: UIColor) {
let startP = self.currentPoint
drawPoint(point: point, color: color, radius: 3)
self.move(to: startP)
}

}

class TestView : UIView {
var step: CGFloat = 1.0;
var yMaximum: CGFloat = 1.0
var xMaximum: CGFloat = 1.0
var data: [CGPoint] = [] {
didSet {
xMaximum = data.reduce(-CGFloat.greatestFiniteMagnitude, { max(\$0, \$1.x) })
yMaximum = data.reduce(-CGFloat.greatestFiniteMagnitude, { max(\$0, \$1.y) })
setNeedsDisplay()
}
}

func scale(point: CGPoint) -> CGPoint {
return  CGPoint(x: bounds.width * point.x / xMaximum ,
y: (bounds.height - bounds.height * point.y / yMaximum))
}

override func draw(_ rect: CGRect) {
if data.count <= 1 {
return
}
let path = cubicCurvedPath()

UIColor.black.setStroke()
path.lineWidth = 1
path.stroke()
}

func cubicCurvedPath() -> UIBezierPath {
let path = UIBezierPath()

var p1 = scale(point: data[0])
path.drawPoint(point: p1, color: UIColor.red, radius: 3)
path.move(to: p1)
var oldControlP = p1

for i in 0..<data.count {
let p2 = scale(point:data[i])
path.drawPoint(point: p2, color: UIColor.red, radius: 3)
var p3: CGPoint? = nil
if i < data.count - 1 {
p3 = scale(point:data [i+1])
}

let newControlP = controlPointForPoints(p1: p1, p2: p2, p3: p3)
//uncomment the following four lines to graph control points
//if let controlP = newControlP {
//    path.drawWithLine(point:controlP, color: UIColor.blue)
//}
//path.drawWithLine(point:oldControlP, color: UIColor.gray)

path.addCurve(to: p2, controlPoint1: oldControlP , controlPoint2: newControlP ?? p2)
oldControlP = imaginFor(point1: newControlP, center: p2) ?? p1
if let p3 = p3 {
if oldControlP.x > p3.x { oldControlP.x = p3.x }
}
//***
p1 = p2
}
return path;
}

func imaginFor(point1: CGPoint?, center: CGPoint?) -> CGPoint? {
//returns "mirror image" of point: the point that is symmetrical through center.
//aka opposite of midpoint; returns the point whose midpoint with point1 is center)
guard let p1 = point1, let center = center else {
return nil
}
let newX = center.x + center.x - p1.x
let newY = center.y + center.y - p1.y

return CGPoint(x: newX, y: newY)
}

func midPointForPoints(p1: CGPoint, p2: CGPoint) -> CGPoint {
return CGPoint(x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2);
}

func clamp(num: CGFloat, bounds1: CGFloat, bounds2: CGFloat) -> CGFloat {
//ensure num is between bounds.
if (bounds1 < bounds2) {
return min(max(bounds1,num),bounds2);
} else {
return min(max(bounds2,num),bounds1);
}
}

func controlPointForPoints(p1: CGPoint, p2: CGPoint, p3: CGPoint?) -> CGPoint? {
guard let p3 = p3 else {
return nil
}

let leftMidPoint  = midPointForPoints(p1: p1, p2: p2)
let rightMidPoint = midPointForPoints(p1: p2, p2: p3)
let imaginPoint = imaginFor(point1: rightMidPoint, center: p2)

var controlPoint = midPointForPoints(p1: leftMidPoint, p2: imaginPoint!)

controlPoint.y = clamp(num: controlPoint.y, bounds1: p1.y, bounds2: p2.y)

let flippedP3 = p2.y + (p2.y-p3.y)

controlPoint.y = clamp(num: controlPoint.y, bounds1: p2.y, bounds2: flippedP3);
controlPoint.x = clamp (num:controlPoint.x, bounds1: p1.x, bounds2: p2.x)
//***
// print ("p1: \(p1), p2: \(p2), p3: \(p3), LM:\(leftMidPoint), RM:\(rightMidPoint), IP:\(imaginPoint), fP3:\(flippedP3), CP:\(controlPoint)")
return controlPoint
}

}

let u = TestView(frame: CGRect(x: 0, y: 0, width: 700, height: 600));
u.backgroundColor = UIColor.white

u.data = [0.5 ° 1, 1 ° 3, 2 ° 5, 4 ° 9, 8 ° 15, 9.4 ° 8, 9.5 ° 10, 12 ° 4, 13 ° 10, 15 ° 3, 16 ° 1]
``````

And the same code in ObjC (more or less. this doesn’t include the drawing routines themselves, and it does allow the points array to include missing data

``````+ (UIBezierPath *)pathWithPoints:(NSArray <NSValue *> *)points open:(BOOL) open {
//based on Roman Filippov code: http://stackoverflow.com/a/40203583/580850
//open means allow gaps in path.
UIBezierPath *path = [UIBezierPath bezierPath];
CGPoint p1 = [points[0] CGPointValue];
[path moveToPoint:p1];
CGPoint oldControlPoint = p1;
for (NSUInteger pointIndex = 1; pointIndex< points.count; pointIndex++) {
CGPoint p2 = [points[pointIndex]  CGPointValue];  //note: mark missing data with CGFloatMax

if (p1.y >= CGFloatMax || p2.y >= CGFloatMax) {
if (open) {
[path moveToPoint:p2];
} else {
}
oldControlPoint = p2;
} else {
CGPoint p3 = CGPointZero;
if (pointIndex +1 < points.count) p3 = [points[pointIndex+1] CGPointValue] ;
if (p3.y >= CGFloatMax) p3 = CGPointZero;
CGPoint newControlPoint = controlPointForPoints2(p1, p2, p3);
if (!CGPointEqualToPoint( newControlPoint, CGPointZero)) {
[path addCurveToPoint: p2 controlPoint1:oldControlPoint controlPoint2: newControlPoint];
oldControlPoint = imaginForPoints( newControlPoint,  p2);
if (! CGPointEqualToPoint(p3,CGPointZero)) {
if (oldControlPoint.x > p3.x ) {
oldControlPoint.x = p3.x;
}
//***
} else {
[path addCurveToPoint: p2 controlPoint1:oldControlPoint controlPoint2: p2];
oldControlPoint = p2;
}
}
p1 = p2;
}
return path;
}

static CGPoint imaginForPoints(CGPoint point, CGPoint center) {
//returns "mirror image" of point: the point that is symmetrical through center.
if (CGPointEqualToPoint(point, CGPointZero) || CGPointEqualToPoint(center, CGPointZero)) {
return CGPointZero;
}
CGFloat newX = center.x + (center.x-point.x);
CGFloat newY = center.y + (center.y-point.y);
if (isinf(newY)) {
newY = BEMNullGraphValue;
}
return CGPointMake(newX,newY);
}

static CGFloat clamp(CGFloat num, CGFloat bounds1, CGFloat bounds2) {
//ensure num is between bounds.
if (bounds1 < bounds2) {
return MIN(MAX(bounds1,num),bounds2);
} else {
return MIN(MAX(bounds2,num),bounds1);
}
}

static CGPoint controlPointForPoints2(CGPoint p1, CGPoint p2, CGPoint p3) {
if (CGPointEqualToPoint(p3, CGPointZero)) return CGPointZero;
CGPoint leftMidPoint = midPointForPoints(p1, p2);
CGPoint rightMidPoint = midPointForPoints(p2, p3);
CGPoint imaginPoint = imaginForPoints(rightMidPoint, p2);
CGPoint controlPoint = midPointForPoints(leftMidPoint, imaginPoint);

controlPoint.y = clamp(controlPoint.y, p1.y, p2.y);

CGFloat flippedP3 = p2.y + (p2.y-p3.y);

controlPoint.y = clamp(controlPoint.y, p2.y, flippedP3);