You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
165 lines
5.3 KiB
165 lines
5.3 KiB
1 year ago
|
//
|
||
|
// UIBezierPath+ThroughPointsBezier.m
|
||
|
// ZHLineChart
|
||
|
//
|
||
|
// Created by 周亚楠 on 2020/3/1.
|
||
|
// Copyright © 2020 Zhou. All rights reserved.
|
||
|
//
|
||
|
|
||
|
#import "UIBezierPath+ThroughPointsBezier.h"
|
||
|
#import <objc/runtime.h>
|
||
|
|
||
|
@implementation UIBezierPath (ThroughPointsBezier)
|
||
|
|
||
|
- (void)setContractionFactor:(CGFloat)contractionFactor
|
||
|
{
|
||
|
objc_setAssociatedObject(self, @selector(contractionFactor), @(contractionFactor), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||
|
}
|
||
|
|
||
|
- (CGFloat)contractionFactor
|
||
|
{
|
||
|
id contractionFactorAssociatedObject = objc_getAssociatedObject(self, @selector(contractionFactor));
|
||
|
if (!contractionFactorAssociatedObject) {
|
||
|
return 0.7;
|
||
|
}
|
||
|
return [contractionFactorAssociatedObject floatValue];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 正常折线绘制
|
||
|
* 必须将CGPoint结构体包装成NSValue对象并且至少一个点来画折线。
|
||
|
*/
|
||
|
- (void)addNormalBezierThroughPoints:(NSArray *)pointArray
|
||
|
{
|
||
|
for (int i = 0; i < pointArray.count; i++) {
|
||
|
|
||
|
NSValue * pointIValue = pointArray[i];
|
||
|
CGPoint pointI = [pointIValue CGPointValue];
|
||
|
[self addLineToPoint:pointI];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)addBezierThroughPoints:(NSArray *)pointArray
|
||
|
{
|
||
|
NSAssert(pointArray.count > 0, @"You must give at least 1 point for drawing the curve.");
|
||
|
|
||
|
if (pointArray.count < 3) {
|
||
|
switch (pointArray.count) {
|
||
|
case 1:
|
||
|
{
|
||
|
NSValue * point0Value = pointArray[0];
|
||
|
CGPoint point0 = [point0Value CGPointValue];
|
||
|
[self addLineToPoint:point0];
|
||
|
}
|
||
|
break;
|
||
|
case 2:
|
||
|
{
|
||
|
NSValue * point0Value = pointArray[0];
|
||
|
CGPoint point0 = [point0Value CGPointValue];
|
||
|
NSValue * point1Value = pointArray[1];
|
||
|
CGPoint point1 = [point1Value CGPointValue];
|
||
|
[self addQuadCurveToPoint:point1 controlPoint:ControlPointForTheBezierCanThrough3Point(self.currentPoint, point0, point1)];
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CGPoint previousPoint = CGPointZero;
|
||
|
|
||
|
CGPoint previousCenterPoint = CGPointZero;
|
||
|
CGPoint centerPoint = CGPointZero;
|
||
|
CGFloat centerPointDistance = 0;
|
||
|
|
||
|
CGFloat obliqueAngle = 0;
|
||
|
|
||
|
CGPoint previousControlPoint1 = CGPointZero;
|
||
|
CGPoint previousControlPoint2 = CGPointZero;
|
||
|
CGPoint controlPoint1 = CGPointZero;
|
||
|
|
||
|
previousPoint = self.currentPoint;
|
||
|
|
||
|
for (int i = 0; i < pointArray.count; i++) {
|
||
|
|
||
|
NSValue * pointIValue = pointArray[i];
|
||
|
CGPoint pointI = [pointIValue CGPointValue];
|
||
|
|
||
|
if (i > 0) {
|
||
|
|
||
|
previousCenterPoint = CenterPointOf(self.currentPoint, previousPoint);
|
||
|
centerPoint = CenterPointOf(previousPoint, pointI);
|
||
|
|
||
|
centerPointDistance = DistanceBetweenPoint(previousCenterPoint, centerPoint);
|
||
|
|
||
|
obliqueAngle = ObliqueAngleOfStraightThrough(centerPoint, previousCenterPoint);
|
||
|
|
||
|
previousControlPoint2 = CGPointMake(previousPoint.x - 0.5 * self.contractionFactor * centerPointDistance * cos(obliqueAngle), previousPoint.y - 0.5 * self.contractionFactor * centerPointDistance * sin(obliqueAngle));
|
||
|
controlPoint1 = CGPointMake(previousPoint.x + 0.5 * self.contractionFactor * centerPointDistance * cos(obliqueAngle), previousPoint.y + 0.5 * self.contractionFactor * centerPointDistance * sin(obliqueAngle));
|
||
|
}
|
||
|
|
||
|
if (i == 1) {
|
||
|
|
||
|
[self addQuadCurveToPoint:previousPoint controlPoint:previousControlPoint2];
|
||
|
}
|
||
|
else if (i > 1 && i < pointArray.count - 1) {
|
||
|
|
||
|
[self addCurveToPoint:previousPoint controlPoint1:previousControlPoint1 controlPoint2:previousControlPoint2];
|
||
|
}
|
||
|
else if (i == pointArray.count - 1) {
|
||
|
|
||
|
[self addCurveToPoint:previousPoint controlPoint1:previousControlPoint1 controlPoint2:previousControlPoint2];
|
||
|
[self addQuadCurveToPoint:pointI controlPoint:controlPoint1];
|
||
|
}
|
||
|
else {
|
||
|
|
||
|
}
|
||
|
|
||
|
previousControlPoint1 = controlPoint1;
|
||
|
previousPoint = pointI;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CGFloat ObliqueAngleOfStraightThrough(CGPoint point1, CGPoint point2) // [-π/2, 3π/2)
|
||
|
{
|
||
|
CGFloat obliqueRatio = 0;
|
||
|
CGFloat obliqueAngle = 0;
|
||
|
|
||
|
if (point1.x > point2.x) {
|
||
|
|
||
|
obliqueRatio = (point2.y - point1.y) / (point2.x - point1.x);
|
||
|
obliqueAngle = atan(obliqueRatio);
|
||
|
}
|
||
|
else if (point1.x < point2.x) {
|
||
|
|
||
|
obliqueRatio = (point2.y - point1.y) / (point2.x - point1.x);
|
||
|
obliqueAngle = M_PI + atan(obliqueRatio);
|
||
|
}
|
||
|
else if (point2.y - point1.y >= 0) {
|
||
|
|
||
|
obliqueAngle = M_PI/2;
|
||
|
}
|
||
|
else {
|
||
|
obliqueAngle = -M_PI/2;
|
||
|
}
|
||
|
|
||
|
return obliqueAngle;
|
||
|
}
|
||
|
|
||
|
CGPoint ControlPointForTheBezierCanThrough3Point(CGPoint point1, CGPoint point2, CGPoint point3)
|
||
|
{
|
||
|
return CGPointMake(2 * point2.x - (point1.x + point3.x) / 2, 2 * point2.y - (point1.y + point3.y) / 2);
|
||
|
}
|
||
|
|
||
|
CGFloat DistanceBetweenPoint(CGPoint point1, CGPoint point2)
|
||
|
{
|
||
|
return sqrt((point1.x - point2.x) * (point1.x - point2.x) + (point1.y - point2.y) * (point1.y - point2.y));
|
||
|
}
|
||
|
|
||
|
CGPoint CenterPointOf(CGPoint point1, CGPoint point2)
|
||
|
{
|
||
|
return CGPointMake((point1.x + point2.x) / 2, (point1.y + point2.y) / 2);
|
||
|
}
|
||
|
|
||
|
@end
|