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.
290 lines
12 KiB
290 lines
12 KiB
1 year ago
|
//
|
||
|
// ZHLineChartView.m
|
||
|
// ZHLineChart
|
||
|
//
|
||
|
// Created by 周亚楠 on 2020/3/1.
|
||
|
// Copyright © 2020 Zhou. All rights reserved.
|
||
|
//
|
||
|
|
||
|
#import "ZHLineChartView.h"
|
||
|
#import "UIBezierPath+ThroughPointsBezier.h"
|
||
|
|
||
|
@interface ZHLineChartView ()
|
||
|
@property (nonatomic, strong) NSMutableArray *pointArr;
|
||
|
@end
|
||
|
|
||
|
@implementation ZHLineChartView
|
||
|
|
||
|
- (instancetype)initWithFrame:(CGRect)frame
|
||
|
{
|
||
|
self = [super initWithFrame:frame];
|
||
|
if (self) {
|
||
|
self.backgroundColor = [UIColor whiteColor];
|
||
|
|
||
|
self.circleRadius = 3.f;
|
||
|
self.lineWidth = 1.5f;
|
||
|
self.horizontalLineWidth = 0.5f;
|
||
|
self.horizontalBottomLineWidth = 1.f;
|
||
|
self.dataTextWidth = 20;
|
||
|
self.leftTextWidth = 25;
|
||
|
self.scaleOffset = 0.f;
|
||
|
self.showCalibration = YES;
|
||
|
self.bottomOffset = 20.f;
|
||
|
self.lineToLeftOffset = 5;
|
||
|
self.angle = M_PI * 1.75;
|
||
|
self.textFontSize = 10;
|
||
|
self.edge = UIEdgeInsetsMake(25, 5, 40, 15);
|
||
|
|
||
|
self.showKeyPoint = YES;
|
||
|
self.circleStrokeColor = KLineColor;
|
||
|
self.circleFillColor = [UIColor whiteColor];
|
||
|
self.textColor = KTextColor;
|
||
|
self.lineColor = KLineColor;
|
||
|
self.horizontalLineColor = KHorizontalLineColor;
|
||
|
self.horizontalBottomLineColor = KHorizontalBottomLineColor;
|
||
|
|
||
|
self.addCurve = YES;
|
||
|
self.toCenter = YES;
|
||
|
self.supplement = NO;
|
||
|
self.showLineData = YES;
|
||
|
self.showColorGradient = YES;
|
||
|
|
||
|
self.showHorizontalLine = YES;
|
||
|
self.horizontalLineStyle = 0;
|
||
|
self.showVerticalLine = YES;
|
||
|
self.verticalLineStyle = 0;
|
||
|
|
||
|
|
||
|
self.colorArr = [NSArray arrayWithObjects:(id)[[self.lineColor colorWithAlphaComponent:0.4] CGColor],(id)[[[UIColor whiteColor] colorWithAlphaComponent:0.1] CGColor], nil];
|
||
|
|
||
|
}
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
|
||
|
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
|
||
|
NSLog(@"开始触摸");
|
||
|
}
|
||
|
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
|
||
|
UITouch* touch = [touches anyObject];
|
||
|
CGPoint currentPoint = [touch locationInView:self]; // 当前视图内的坐标
|
||
|
NSLog(@"移动的点 currentPointX: %f currentPointY: %f", currentPoint.x, currentPoint.y);
|
||
|
}
|
||
|
/**
|
||
|
* 渲染折线图
|
||
|
*/
|
||
|
- (void)drawLineChart
|
||
|
{
|
||
|
NSMutableArray *pointArr = [NSMutableArray array];
|
||
|
CGFloat labelHeight = self.textFontSize;
|
||
|
NSInteger numSpace = (self.max.integerValue - self.min.integerValue) / self.splitCount;
|
||
|
CGFloat spaceY = (self.frame.size.height - self.edge.top - self.edge.bottom - ((self.splitCount + 1) * labelHeight)) / self.splitCount;
|
||
|
CGFloat minMidY = 0.f;
|
||
|
CGFloat maxMidY;
|
||
|
|
||
|
for (int i = 0; i < self.splitCount + 1; i ++) {
|
||
|
//创建纵轴文本
|
||
|
UILabel *leftLabel = [[UILabel alloc] init];
|
||
|
leftLabel.frame = CGRectMake(self.edge.left, self.leftTextWidth + (spaceY + labelHeight) * i, self.leftTextWidth, labelHeight);
|
||
|
leftLabel.textColor = self.textColor;
|
||
|
leftLabel.textAlignment = NSTextAlignmentRight;
|
||
|
leftLabel.font = [UIFont systemFontOfSize:self.textFontSize];
|
||
|
NSInteger leftNum = self.max.integerValue - numSpace * i;
|
||
|
if (i == self.splitCount) {
|
||
|
leftNum = self.min.integerValue;
|
||
|
}
|
||
|
leftLabel.text = [NSString stringWithFormat:@"%ld",leftNum];
|
||
|
[self addSubview:leftLabel];
|
||
|
|
||
|
if (!i) {
|
||
|
minMidY = CGRectGetMidY(leftLabel.frame);
|
||
|
}
|
||
|
|
||
|
UIBezierPath *linePath = [UIBezierPath bezierPath];
|
||
|
CGFloat minX = CGRectGetMaxX(leftLabel.frame) + self.lineToLeftOffset;
|
||
|
CGFloat maxX = CGRectGetMaxX(self.frame) - self.edge.right;
|
||
|
[linePath moveToPoint:CGPointMake(minX, CGRectGetMidY(leftLabel.frame))];
|
||
|
[linePath addLineToPoint:CGPointMake(maxX, CGRectGetMidY(leftLabel.frame))];
|
||
|
|
||
|
CAShapeLayer *hLineLayer = [CAShapeLayer layer];
|
||
|
if (i == self.splitCount) {
|
||
|
hLineLayer.strokeColor = self.horizontalBottomLineColor.CGColor;
|
||
|
hLineLayer.lineWidth = self.horizontalBottomLineWidth;
|
||
|
|
||
|
CGFloat spaceX = (maxX - minX) / (self.horizontalDataArr.count - (self.toCenter ? 0 : 1));
|
||
|
maxMidY = CGRectGetMidY(leftLabel.frame);
|
||
|
if(self.showCalibration){ //是否需要刻度
|
||
|
//创建刻度
|
||
|
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
|
||
|
NSInteger count = self.horizontalDataArr.count;
|
||
|
if (self.toCenter) {
|
||
|
count = self.horizontalDataArr.count + 1;
|
||
|
}
|
||
|
for (int j = 0 ; j < count; j ++) {
|
||
|
[bezierPath moveToPoint:CGPointMake(minX + spaceX * j, maxMidY + self.scaleOffset)];
|
||
|
[bezierPath addLineToPoint:CGPointMake(minX + spaceX * j, maxMidY + 2 + self.scaleOffset)];
|
||
|
[linePath appendPath:bezierPath];
|
||
|
}
|
||
|
}
|
||
|
//创建横轴文本
|
||
|
CGFloat bottomLabelWidth = spaceX + 20;
|
||
|
CGFloat ratio = (maxMidY - minMidY) / (self.max.floatValue - self.min.floatValue);
|
||
|
for (int k = 0; k < self.horizontalDataArr.count; k ++) {
|
||
|
CGFloat midX = minX + (spaceX * k) + (self.toCenter ? spaceX / 2 : 0);
|
||
|
UILabel *bottomLabel = [[UILabel alloc] init];
|
||
|
bottomLabel.frame = CGRectMake(midX - bottomLabelWidth / 2, maxMidY + self.bottomOffset, bottomLabelWidth, labelHeight);
|
||
|
bottomLabel.textColor = self.textColor;
|
||
|
bottomLabel.textAlignment = NSTextAlignmentCenter;
|
||
|
bottomLabel.font = [UIFont systemFontOfSize:self.textFontSize];
|
||
|
bottomLabel.text = self.horizontalDataArr[k];
|
||
|
[self addSubview:bottomLabel];
|
||
|
//旋转
|
||
|
bottomLabel.transform = CGAffineTransformMakeRotation(self.angle);
|
||
|
|
||
|
//构造关键点
|
||
|
NSNumber *tempNum = self.lineDataAry[k];
|
||
|
CGFloat y = maxMidY - (tempNum.integerValue - self.min.floatValue) * ratio;
|
||
|
if (self.toCenter && self.supplement && !k) {
|
||
|
NSValue *value = [NSValue valueWithCGPoint:CGPointMake(minX, y)];
|
||
|
[pointArr addObject:value];
|
||
|
}
|
||
|
NSValue *value = [NSValue valueWithCGPoint:CGPointMake(midX, y)];
|
||
|
[pointArr addObject:value];
|
||
|
if (self.toCenter && self.supplement && k == self.lineDataAry.count - 1) {
|
||
|
NSValue *value = [NSValue valueWithCGPoint:CGPointMake(maxX, y)];
|
||
|
[pointArr addObject:value];
|
||
|
}
|
||
|
}
|
||
|
//绘制折线
|
||
|
[self drawLineLayerWithPointArr:pointArr maxMidY:maxMidY];
|
||
|
|
||
|
} else {
|
||
|
hLineLayer.strokeColor = self.horizontalLineColor.CGColor;
|
||
|
hLineLayer.lineWidth = self.horizontalLineWidth;
|
||
|
}
|
||
|
hLineLayer.path = linePath.CGPath;
|
||
|
hLineLayer.fillColor = [UIColor clearColor].CGColor;
|
||
|
hLineLayer.lineCap = kCALineCapRound;
|
||
|
hLineLayer.lineJoin = kCALineJoinRound;
|
||
|
hLineLayer.contentsScale = [UIScreen mainScreen].scale;
|
||
|
[self.layer addSublayer:hLineLayer];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 绘制折线及渐变
|
||
|
*/
|
||
|
- (void)drawLineLayerWithPointArr:(NSMutableArray *)pointArr maxMidY:(CGFloat)maxMidY
|
||
|
{
|
||
|
CGPoint startPoint = [[pointArr firstObject] CGPointValue];
|
||
|
CGPoint endPoint = [[pointArr lastObject] CGPointValue];
|
||
|
UIBezierPath *linePath = [UIBezierPath bezierPath];
|
||
|
[linePath moveToPoint:startPoint];
|
||
|
if (self.addCurve) {
|
||
|
[linePath addBezierThroughPoints:pointArr];
|
||
|
} else {
|
||
|
[linePath addNormalBezierThroughPoints:pointArr];
|
||
|
}
|
||
|
|
||
|
CAShapeLayer *lineLayer = [CAShapeLayer layer];
|
||
|
lineLayer.path = linePath.CGPath;
|
||
|
lineLayer.strokeColor = self.lineColor.CGColor;
|
||
|
lineLayer.fillColor = [UIColor clearColor].CGColor;
|
||
|
lineLayer.lineWidth = self.lineWidth;
|
||
|
lineLayer.lineCap = kCALineCapRound;
|
||
|
lineLayer.lineJoin = kCALineJoinRound;
|
||
|
lineLayer.contentsScale = [UIScreen mainScreen].scale;
|
||
|
[self.layer addSublayer:lineLayer];
|
||
|
|
||
|
//颜色渐变
|
||
|
if (self.showColorGradient) {
|
||
|
UIBezierPath *colorPath = [UIBezierPath bezierPath];
|
||
|
colorPath.lineWidth = 1.f;
|
||
|
[colorPath moveToPoint:startPoint];
|
||
|
if (self.addCurve) {
|
||
|
[colorPath addBezierThroughPoints:pointArr];
|
||
|
} else {
|
||
|
[colorPath addNormalBezierThroughPoints:pointArr];
|
||
|
}
|
||
|
[colorPath addLineToPoint:CGPointMake(endPoint.x, maxMidY)];
|
||
|
[colorPath addLineToPoint:CGPointMake(startPoint.x, maxMidY)];
|
||
|
[colorPath addLineToPoint:CGPointMake(startPoint.x, startPoint.y)];
|
||
|
|
||
|
CAShapeLayer *bgLayer = [CAShapeLayer layer];
|
||
|
bgLayer.path = colorPath.CGPath;
|
||
|
bgLayer.frame = self.bounds;
|
||
|
|
||
|
CAGradientLayer *colorLayer = [CAGradientLayer layer];
|
||
|
colorLayer.frame = bgLayer.frame;
|
||
|
colorLayer.mask = bgLayer;
|
||
|
colorLayer.startPoint = CGPointMake(0, 0);
|
||
|
colorLayer.endPoint = CGPointMake(0, 1);
|
||
|
colorLayer.colors = self.colorArr;
|
||
|
[self.layer addSublayer:colorLayer];
|
||
|
}
|
||
|
|
||
|
|
||
|
// CABasicAnimation *maxPathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
|
||
|
// maxPathAnimation.duration = 12;
|
||
|
// maxPathAnimation.repeatCount = 1;
|
||
|
// maxPathAnimation.removedOnCompletion = YES;
|
||
|
// maxPathAnimation.fromValue = @0.f;
|
||
|
// maxPathAnimation.toValue = @1;
|
||
|
// [maxLineLayer addAnimation:maxPathAnimation forKey:@"strokeEnd"];
|
||
|
//绘制关键折线及关键点
|
||
|
if(self.showKeyPoint){
|
||
|
[self buildDotWithPointsArr:pointArr];
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 绘制关键点及关键点数据展示
|
||
|
*/
|
||
|
- (void)buildDotWithPointsArr:(NSMutableArray *)pointsArr
|
||
|
{
|
||
|
for (int i = 0; i < pointsArr.count; i ++) {
|
||
|
if (self.toCenter && self.supplement && (!i || i == pointsArr.count - 1)) {
|
||
|
continue;
|
||
|
}
|
||
|
NSValue *point = pointsArr[i];
|
||
|
|
||
|
//关键点绘制
|
||
|
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(point.CGPointValue.x, point.CGPointValue.y) radius:self.circleRadius startAngle:0 endAngle:M_PI * 2 clockwise:NO];
|
||
|
CAShapeLayer *circleLayer = [CAShapeLayer layer];
|
||
|
circleLayer.path = path.CGPath;
|
||
|
circleLayer.strokeColor = self.circleStrokeColor.CGColor;
|
||
|
circleLayer.fillColor = self.circleFillColor.CGColor;
|
||
|
circleLayer.lineWidth = self.lineWidth;
|
||
|
circleLayer.lineCap = kCALineCapRound;
|
||
|
circleLayer.lineJoin = kCALineJoinRound;
|
||
|
circleLayer.contentsScale = [UIScreen mainScreen].scale;
|
||
|
[self.layer addSublayer:circleLayer];
|
||
|
|
||
|
//关键点数据
|
||
|
if (self.showLineData) {
|
||
|
UILabel *numLabel = [[UILabel alloc] init];
|
||
|
numLabel.frame = CGRectMake(point.CGPointValue.x - self.dataTextWidth / 2, point.CGPointValue.y - 18, self.dataTextWidth, self.textFontSize);
|
||
|
numLabel.textColor = self.textColor;
|
||
|
numLabel.textAlignment = NSTextAlignmentRight;
|
||
|
numLabel.font = [UIFont systemFontOfSize:self.textFontSize];
|
||
|
NSInteger index = i;
|
||
|
if (self.toCenter && self.supplement) {
|
||
|
index = i - 1;
|
||
|
}
|
||
|
numLabel.text = [NSString stringWithFormat:@"%@",self.lineDataAry[index]];
|
||
|
[self addSubview:numLabel];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
// Only override drawRect: if you perform custom drawing.
|
||
|
// An empty implementation adversely affects performance during animation.
|
||
|
- (void)drawRect:(CGRect)rect {
|
||
|
// Drawing code
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
@end
|