में एक बेहतर एल्गोरिथ्म लागू, मैं कोड कुछ सरल बनाया। यहां एक पूर्ण स्विफ्ट खेल का मैदान संस्करण है, और उसके नीचे एक ओबीजेसी संस्करण है।
मुझे पता चला कि यदि एक्स वृद्धि नियमित नहीं है, तो मूल एल्गोरिदम कुछ दुर्भाग्यपूर्ण रेट्रोग्रेड लाइन बनाता है जब अंक एक्स अक्ष पर एक साथ बंद होते हैं। केवल निम्नलिखित मान-मान से अधिक नहीं होने के लिए नियंत्रण बिंदुओं को बाधित करना समस्या को ठीक करने लगता है, हालांकि मेरे पास इसके लिए कोई गणितीय औचित्य नहीं है, केवल प्रयोग। // ** जोड़े गए 'के रूप में चिह्नित दो खंड हैं जो इस परिवर्तन को लागू करते हैं।
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;
}
+1 के लिए प्रतीक्षा कर रहे हैं आपके परिणाम अच्छे लग रहे हैं। उस समय मैंने चिकनी साइनसॉइडल वक्र खींचने के लिए https://github.com/amogh-talpallikar/smooth-curve-algorithm का उपयोग किया। हालांकि आपके प्रयास के लिए हैट्स। –