2012-12-05 56 views
12

के साथ ग्राफ़ घटता ड्रा करें मैं अपने एप्लिकेशन में एक ग्राफ खींच रहा हूं। मेरी समस्या यह है कि मैं वक्र के रूप में वर्टेक्स अंक में शामिल होने वाली रेखा खींचना चाहता हूं। वर्तमान में मैं उन्हें UIBezierPath के फ़ंक्शन addLineToPoint: के साथ चित्रित कर रहा हूं। मैं उन्हें वक्र के रूप में आकर्षित करना चाहता हूं। मुझे अच्छी तरह पता है कि UIBezierPath में इस सुविधा का समर्थन करने के लिए निम्नलिखित दो कार्य हैं।UIBezierPath

घन वक्र: addCurveToPoint:controlPoint1:controlPoint2: द्विघात वक्र: addQuadCurveToPoint:controlPoint:

लेकिन समस्या यह है कि मैं नियंत्रण बिंदुओं की जरूरत नहीं है है। मेरे पास दो अंत बिंदु हैं। न तो मुझे नियंत्रण बिंदु निर्धारित करने के लिए एक विधि/सूत्र मिला। क्या कोई यहां मेरी सहायता करेगा? अगर कोई कुछ विकल्प सुझा सकता है तो मैं सराहना करूंगा ...

उत्तर

8

इसलिए मुझे @ फोगमेस्टर के उत्तर के आधार पर एक काम मिल गया।

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))); 
     } 

     [path addQuadCurveToPoint:p2 controlPoint:centerPoint]; 
     p1 = p2; 
    } 

    [path stroke]; 
+1

+1 के लिए प्रतीक्षा कर रहे हैं आपके परिणाम अच्छे लग रहे हैं। उस समय मैंने चिकनी साइनसॉइडल वक्र खींचने के लिए https://github.com/amogh-talpallikar/smooth-curve-algorithm का उपयोग किया। हालांकि आपके प्रयास के लिए हैट्स। –

12

यदि आपके पास केवल दो अंक हैं तो आप वास्तव में उन्हें वक्र में बाहर नहीं निकाल सकते हैं।

यदि आपके पास दो से अधिक अंक हैं (यानी वक्र के साथ कई बिंदु) तो आप मोटे तौर पर उनका अनुसरण करने के लिए एक वक्र खींच सकते हैं।

आप निम्नलिखित ...

ठीक है, तो आप 5 अंक का कहना है। पी 1, पी 2, पी 3, पी 4, और पी 5। आपको अंक की प्रत्येक जोड़ी के बीच मध्यबिंदु को काम करने की आवश्यकता है। एम 1 = पी 1 और पी 2 (और इतने पर) के मध्य बिंदु ...

तो आपके पास एम 1, एम 2, एम 3, और एम 4 है।

अब आप वक्र के वर्गों और एक ट्रैक्टर की अवस्था के लिए नियंत्रण बिंदुओं के रूप में अंक के अंत अंक के रूप में मध्य अंक का उपयोग कर सकते हैं ...

तो ...

बात करने के लिए ले जाएँ एम 1। नियंत्रण बिंदु के रूप में पी 2 के साथ m2 को इंगित करने के लिए चौकोर वक्र जोड़ें।

नियंत्रण बिंदु के रूप में पी 3 के साथ m3 को इंगित करने के लिए चौकोर वक्र जोड़ें।

नियंत्रण बिंदु के रूप में पी 4 के साथ m4 को इंगित करने के लिए चौकोर वक्र जोड़ें।

और इतने पर ...

यह आप सभी की अवस्था के सिरों से अलग हो जाएगा (कैसे पल में उन्हें प्राप्त करने के लिए, माफ करना याद नहीं कर सकते)।

+0

लेकिन भाई के बारे में बढ़ रही है और घटते प्रभाव के लिए बदल दिया। poits का उदाहरण y-cordinates के लिए कर रहे हैं y1 = 10, y2 = 15, वाई = 8। अब वक्र y1-> y2 चाहिए लाइन और वक्र y2-> y3 sh से ऊपर हो रेखा से नीचे होना चाहिए। इसका मतलब है कि हम केवल मध्य बिंदु को नियंत्रण बिंदु के रूप में नहीं डाल सकते हैं, हमें मध्य बिंदुओं को ऊपर/नीचे भी अनुवाद करने की आवश्यकता है। –

+0

आप सही हैं, यह केवल वक्र का अनुमान है। अंक एक साथ हैं, वक्र अधिक सटीक। इसके अलावा, वक्र के ढाल में परिवर्तन जितना तेज़ होगा उतना ही वास्तविक बिंदुओं से दूर होगा। हालांकि यह काफी सटीक अनुमान प्राप्त करने का सबसे तेज़, आसान तरीका है। अधिक सटीकता प्राप्त करने के लिए और अधिक कठिन गणित की आवश्यकता होगी और इसमें बहुत अधिक आवश्यकता होगी। आंखों की तुलना में कोड में वक्र को निकालने के लिए बहुत कठिन है: डी – Fogmeister

+0

हैलो @ फ़ोगमेस्टर, मैं वही कर रहा हूं जैसा आपने ऊपर सुझाया है, लेकिन मुझे बहुत चिकनी वक्र नहीं मिलते हैं। यहां लिंक http://stackoverflow.com/questions/20881721/calculate-controlpoints-while-drawing-in-ios है। बेहतर क्या किया जा सकता है। मैं आपसे अनुरोध करता हूं कि कृपया मेरी मदद करें। फोगमेस्टर – Ranjit

3

एक अच्छा समाधान आपके अंक के माध्यम से सीधे UIBezierPath बनाने और फिर रेखा को घुमाने के लिए एक स्पलीन का उपयोग करना होगा। यह अन्य उत्तर देखें जो UIBezierPath के लिए एक श्रेणी देता है जो कैटमुल-रोम स्पलीन करता है। Drawing Smooth Curves - Methods Needed

5

मैंने इस पर थोड़ा सा काम किया है और कैटमुल-रोम स्पलीन से बहुत अच्छे परिणाम प्राप्त किए हैं। यहां एक विधि है जो CGPoints (NSValues ​​के रूप में संग्रहीत) की एक सरणी लेती है और दिए गए दृश्य में रेखा जोड़ती है।

- (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:pi]; 
      } 
     } 

     [path addLineToPoint:point2]; 
    } 

    [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]; 

    [view.layer addSublayer:shapeView]; 
} 

मैं इसे यहाँ https://github.com/johnyorke/JYGraphViewController

20

का उपयोग दिसंबर 5 '12 पर आबिद हुसैन के जवाब पर विस्तार करें।

मैं कोड implemeted, और यह काम किया, लेकिन नतीजा यह है कि तरह देखा: enter image description here

क्या मैं addQuadCurveToPoint था करने के लिए: enter image description here

थोड़ा समायोजन के साथ

, मैं मैं क्या चाहता था प्राप्त करने में सक्षम था मध्य बिंदुओं के साथ-साथ मध्य बिंदुओं को नियंत्रण बिंदु के रूप में उपयोग करना। एबिड के नमूने के आधार पर कोड नीचे दिया गया है; उम्मीद है कि यह किसी की मदद करता है।

+ (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]; 
     [path addLineToPoint:p2]; 
     return path; 
    } 

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

     CGPoint midPoint = midPointForPoints(p1, p2); 
     [path addQuadCurveToPoint:midPoint controlPoint:controlPointForPoints(midPoint, p1)]; 
     [path addQuadCurveToPoint:p2 controlPoint:controlPointForPoints(midPoint, 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; 
} 
+1

के लिए आपके उत्तर –

+0

यह कैसा दिखता है यदि आपके पास दो अंक थे जो लगातार घटते थे? अर्थात। 1, 5, 3, 10 के बजाय आपके पास 3, 1, 5, 10 था? क्या यह चिकनी वृद्धि ठीक से दिखाएगा? – Fogmeister

+0

@Fogmeister यह नहीं होगा। मैंने इस एल्गोरिदम की कोशिश की है और जब यह ऊपर और नीचे स्ट्रोक को बदलने के लिए बहुत अच्छी तरह से काम करता है, तो यह मूल्य सीढ़ियों की तरह होता है यदि मान 6, 10, 12, 18 की तरह जाते हैं। हालांकि प्रयास के लिए बहुत धन्यवाद! –

10

@ user1244109 के जवाब का उपयोग करते हुए मैं जबकि @ रोमन-Filippov से कोड का विश्लेषण करने स्विफ्ट 3. chart with my implementation

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) { 

    let path = quadCurvedPath() 

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

func quadCurvedPath() -> UIBezierPath { 
    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) { 
    let ovalPath = UIBezierPath(ovalIn: CGRect(x: point.x - radius, y: point.y - radius, width: radius * 2, height: radius * 2)) 
    color.setFill() 
    ovalPath.fill() 
} 
+0

बहुत अच्छा! मैं ओबीजेसी को बैकपोर्ट कर रहा हूं और उसके कुछ सवाल हैं। संकल्पनात्मक रूप से, कल्पना क्या करती है? दूसरा, पेट क्यों लेते हैं और फिर सशर्त रूप से -1 से गुणा करते हैं? (उदाहरण के लिए कल्पना में, बस 'newY = center.y * 2 - p1.y' को नए एक्सएक्स की तरह दें) – mackworth

+1

बहुत बढ़िया .. मुझे बहुत सारे घंटे बचाएं .. अच्छा काम इसे जारी रखें .. धन्यवाद ....... । – gamal

1

में एक बेहतर एल्गोरिथ्म लागू, मैं कोड कुछ सरल बनाया। यहां एक पूर्ण स्विफ्ट खेल का मैदान संस्करण है, और उसके नीचे एक ओबीजेसी संस्करण है।

मुझे पता चला कि यदि एक्स वृद्धि नियमित नहीं है, तो मूल एल्गोरिदम कुछ दुर्भाग्यपूर्ण रेट्रोग्रेड लाइन बनाता है जब अंक एक्स अक्ष पर एक साथ बंद होते हैं। केवल निम्नलिखित मान-मान से अधिक नहीं होने के लिए नियंत्रण बिंदुओं को बाधित करना समस्या को ठीक करने लगता है, हालांकि मेरे पास इसके लिए कोई गणितीय औचित्य नहीं है, केवल प्रयोग। // ** जोड़े गए 'के रूप में चिह्नित दो खंड हैं जो इस परिवर्तन को लागू करते हैं।

import UIKit 
import PlaygroundSupport 

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

extension UIBezierPath { 
    func drawPoint(point: CGPoint, color: UIColor, radius: CGFloat) { 
     let ovalPath = UIBezierPath(ovalIn: CGRect(x: point.x - radius, y: point.y - radius, width: radius * 2, height: radius * 2)) 
     color.setFill() 
     ovalPath.fill() 
    } 

    func drawWithLine (point: CGPoint, color: UIColor) { 
     let startP = self.currentPoint 
     self.addLine(to: point) 
     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 
      //***added to algorithm 
      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); 
     //***added: 
     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 

PlaygroundPage.current.liveView = u 
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] 

और ObjC में एक ही कोड (कम या ज्यादा। इस में शामिल नहीं है ड्राइंग खुद को दिनचर्या, और यह अंक सरणी लापता डेटा

+ (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 { 
       [path addLineToPoint:p2]; 
      } 
      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); 
       //**added to algorithm 
       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); 
    //**added to algorithm 
    controlPoint.x = clamp(controlPoint.x, p1.x, p2.x); 
    //** 
    return controlPoint; 
} 
2

यहाँ है @ user1244109 शामिल करने के लिए अनुमति नहीं है ' रों जवाब क्रम में स्विफ्ट 3

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

    path.move(to: p1) 

    if points.count == 2 { 
    path.addLine(to: points[1]) 
    return path 
    } 

    for i in 0..<points.count { 
    let mid = midPoint(for: (p1, points[i])) 
    path.addQuadCurve(to: mid, 
        controlPoint: controlPoint(for: (mid, p1))) 
    path.addQuadCurve(to: points[i], 
        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 
}