iOS开发之自定义曲线图
前言:日常开发过程中,经常碰到数据图表类的需求,大部分UI设计会给出一套很漂亮的设计稿,如果选择使用第三方图表库,很多时候很难满足UI设计的需求,与其研究第三方库,不如花点时间自己动手写一个,自己写的代码,可以近100%的还原UI设计稿,何乐而不为?
需求:给定一组数据(x,y),要求画出一个曲线图,可以明确看出数据的变化趋势,并添加点击响应事件,查看具体数值,对应的y轴可以是正数,小数,百分比,正负数。
效果预览:
preview.gif
思路:
1.因为是在手机上显示,所以这里选择将y轴做一个n等分,在给定数据中(本demo是模拟20组数据)取出最大值,计算出y轴刻度;
2.横坐标一般都是日期,给一个固定间隔,用UILabel显示出来就好;
3.根据每一组数据的y值与最大值的比例,计算每一组数据在坐标系中的位置,并记录每一数据在坐标系中的点;
4.在每一个点的位置创建一个UIButton,用于点击响应事件;
5.利用UIBezierPath+CAGradientLayer+CABasicAnimation完成曲线图的动态绘制,并添加遮罩层;
实现:
这里主要介绍构建xy坐标系,获取对应点,动态完成曲线绘制,详细实现可自行下载Demo。
1.首先确定y轴,这里选择将y轴8等分,也就是说y轴上有9个刻度,一般是从0开始,以max_y/8(max_y是所有数据中y轴的最大值)的刻度递增:
//灰色背景线条 + 纵坐标
-(void)initBaseLineViewWithArrayY:(NSMutableArray *)array{for (int i = 0;
i < baseLineCount;
i++) {
//灰色背景线条
UIImageView * lineView = [[UIImageView alloc]init];
lineView.backgroundColor = [UIColor colorWithHex:0xE2EBF2];
[self.bgScrollView addSubview:lineView];
if (i == baseLineCount -1) {
lastLineView = lineView;
}
//纵坐标
UILabel * labelY = [[UILabel alloc]init];
labelY.textAlignment = NSTextAlignmentCenter;
labelY.textColor = [UIColor colorWithHex:0x999999];
labelY.font = [UIFont systemFontOfSize:11];
if (minValue < 0) {
if (i <4) {
//y坐标间隔
int interval = maxValue/4.0;
labelY.text = [NSString stringWithFormat:@"%ld",((baseLineCount-4) - i -1)*interval];
}
if (i == 4) {
labelY.text = @"0";
}
if (i > 4) {
//y坐标间隔
int interval = -minValue/4.0;
labelY.text = [NSString stringWithFormat:@"%ld",((baseLineCount-4) - i -1)*interval];
}
}else{//y坐标间隔
if (_type == 12) {
CGFloat interval = maxValue/8.0;
labelY.text = [NSString stringWithFormat:@"%.2f",(baseLineCount - i -1)*interval];
}else{
int interval = maxValue/8.0;
labelY.text = [NSString stringWithFormat:@"%ld",(baseLineCount - i -1)*interval];
}
}
[self addSubview:labelY];
[lineView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.bgScrollView.mas_top).with.offset(8+i*23);
make.left.equalTo(self.bgScrollView.mas_left).with.offset(0.0f);
make.right.equalTo(self.mas_right).with.offset(22.0f);
make.height.equalTo(@0.5f);
}];
[labelY mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.mas_left).with.offset(0.0f);
make.right.equalTo(self.bgScrollView.mas_left).with.offset(5.0f);
make.centerY.equalTo(lineView);
make.height.equalTo(@17.0f);
}];
}
}
2.x轴坐标相对简单,因为只有日期,等间隔显示就行:
//横坐标
-(void)initXLineWithArrayX:(NSMutableArray *)array{
//横坐标
for (int i = 0;
i < array.count;
i++) {UILabel * labelX = [[UILabel alloc]init];
labelX.textAlignment = NSTextAlignmentCenter;
labelX.textColor = [UIColor colorWithHex:0x999999];
labelX.font = [UIFont systemFontOfSize:12];
labelX.text = [NSString stringWithFormat:@"%@",array[i]];
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformRotate(transform, M_PI / 180.0 * 40);
transform = CGAffineTransformTranslate(transform, 20, 0);
labelX.layer.affineTransform = transform;
[self.bgScrollView addSubview:labelX];
[labelX mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.bgScrollView.mas_left).with.offset(i * (45));
make.top.equalTo(lastLineView.mas_bottom).with.offset(14.0f);
make.width.greaterThanOrEqualTo(@0.0f);
make.height.equalTo(@17.0);
}];
[xLabelArray addObject:labelX];
}
}
3.数据处理(将x轴,y轴分开处理),提取最值,设置默认偏移量等:
-(void)setArrayX:(NSMutableArray *)arrayX{_arrayX = arrayX;
}-(void)setArrayY:(NSMutableArray *)arrayY{_arrayY = arrayY;
//处理最大值
maxValue = https://www.it610.com/article/[[_arrayY valueForKeyPath:@"@max.floatValue"] floatValue];
//处理最小值
minValue = https://www.it610.com/article/[[_arrayY valueForKeyPath:@"@min.floatValue"] floatValue];
if (minValue < 0) {
minValue = https://www.it610.com/article/minValue - 1;
int minRemainder = (int)(-minValue)%4;
minValue = minValue + (minRemainder - 4);
//+1 消除浮点型数据带来的误差
maxValue = maxValue + 1;
//对4取余数
int remainder = (int)maxValue%4;
//确保maxValue能被8整除
maxValue = maxValue + (4 - remainder);
}else{
//当绩效比最大值小于0.1, maxValue = 0.1
if (_type == 12) {if (maxValue < 0.1) {
maxValue = 0.1;
}
}else{
//+1 消除浮点型数据带来的误差
maxValue = maxValue + 1;
//对8取余数
int remainder = (int)maxValue%8;
//确保maxValue能被8整除
maxValue = maxValue + (8 - remainder);
}
}
//背景线条 + Y轴
[self initBaseLineViewWithArrayY:nil];
//X轴
[self initXLineWithArrayX:_arrayX];
self.bgScrollView.contentSize = CGSizeMake( (_arrayX.count)*50, self.bounds.size.height - 60);
self.bgScrollView.contentOffset = CGPointMake((_arrayX.count)*50 - CGRectGetWidth(self.bounds), 0);
//获取拐点
[self getInflectionPointWithArrayX:nil ArrayY:_arrayY color:0x4162FF];
}
4.获取每组数据在xy坐标系中的位置(点),并绘制曲线图,添加动画:
//获取拐点
-(void)getInflectionPointWithArrayX:(NSMutableArray *)xArray ArrayY:(NSMutableArray *)yArray color:(long)color{[self layoutIfNeeded];
NSMutableArray * midPointArray = [NSMutableArray arrayWithCapacity:0];
for (UILabel * label in xLabelArray) {
NSValue *point = [NSValue valueWithCGPoint:CGPointMake(label.center.x, 0)];
[midPointArray addObject:point];
}
for (int i = 0;
i < yArray.count;
i++) {CGFloat currentData = https://www.it610.com/article/[yArray[i] floatValue];
CGFloat possionY = 0;
if (minValue < 0) {
if (currentData>= 0) {
possionY = (92/maxValue)*(maxValue - currentData) + 8;
}else{
possionY = 92 + (92/(minValue))*(currentData) + 8;
}}else{
possionY = (184/maxValue)*(maxValue - currentData) + 8;
}NSValue * point = midPointArray[i];
CGPointOriginPoint = point.CGPointValue;
OriginPoint.y = possionY;
NSValue * newPoint = [NSValue valueWithCGPoint:OriginPoint];
[pointArray addObject:newPoint];
//点击buton
UIButton * pointBtn = [UIButton buttonWithType:UIButtonTypeCustom];
pointBtn.frame = CGRectMake(0, 0, 30, 30);
[pointBtn setImage:[UIImage imageNamed:@"dpoint_blue"] forState:UIControlStateNormal];
[pointBtn setImage:[UIImage imageNamed:@"dpoint_blue_select"] forState:UIControlStateSelected];
[pointBtn addTarget:self action:@selector(clickButtonAction:) forControlEvents:UIControlEventTouchUpInside];
pointBtn.center = OriginPoint;
pointBtn.tag = i + 1000;
[btnArray addObject:pointBtn];
UIButton * messageBtn = [UIButton buttonWithType:UIButtonTypeCustom];
messageBtn.frame = CGRectMake(0, 0, 65, 39);
messageBtn.tag = i + 2000;
messageBtn.hidden = YES;
messageBtn.titleLabel.font = [UIFont systemFontOfSize:12];
messageBtn.userInteractionEnabled = NO;
messageBtn.titleLabel.adjustsFontSizeToFitWidth = YES;
//大于-1小于1,保留四位小数,大于0.01,保留2位小数
if ([_arrayY[i] floatValue] < 1 &&[_arrayY[i] floatValue] > -1) {[messageBtn setTitle:[NSString stringWithFormat:@" %@ ",[Helper notRounding:_arrayY[i] afterPoint:4]] forState:UIControlStateNormal];
}else{
[messageBtn setTitle:[NSString stringWithFormat:@" %@ ",[Helper notRounding:_arrayY[i] afterPoint:2]] forState:UIControlStateNormal];
}
if ([yArray[i] floatValue] > maxValue/2.0f) {[messageBtn setBackgroundImage:[UIImage imageNamed:@"icon_message_top"] forState:UIControlStateNormal];
messageBtn.titleEdgeInsets = UIEdgeInsetsMake(0, 0, -8, 0);
messageBtn.center = CGPointMake(OriginPoint.x, OriginPoint.y + 25);
}else{[messageBtn setBackgroundImage:[UIImage imageNamed:@"icon_message_down"] forState:UIControlStateNormal];
messageBtn.titleEdgeInsets = UIEdgeInsetsMake(0, 0, 8, 0);
messageBtn.center = CGPointMake(OriginPoint.x, OriginPoint.y-25);
}[labelArray addObject:messageBtn];
}//曲线
//起点往前偏移17.5
CGPointoriginP = [[pointArray objectAtIndex:0] CGPointValue];
CGPoint p1 = CGPointMake(originP.x - 27.5, originP.y);
//CGPoint p1 = [[pointArray objectAtIndex:0] CGPointValue];
NSMutableArray * newPointArray = [NSMutableArray arrayWithArray:pointArray];
NSValue *point = [NSValue valueWithCGPoint:p1];
[newPointArray insertObject:point atIndex:0];
//直线的连线
UIBezierPath *beizer = [UIBezierPath bezierPath];
//beizer.
[beizer moveToPoint:p1];
/*遮罩*/
UIBezierPath *bezier1 = [UIBezierPath bezierPath];
bezier1.lineCapStyle = kCGLineCapRound;
bezier1.lineJoinStyle = kCGLineJoinMiter;
[bezier1 moveToPoint:p1];
for (int i = 0;
i
【iOS开发之自定义曲线图】PS:这里只是贴出实现该曲线图的核心代码,纵坐标是四种类型,正数,小数,百分比,正负数,其中百分比稍微特殊一点,代码虽然粗糙,但是毕竟是自己写的,想要什么样式就改成什么样式,没有啥局限性,具体实现详见Demo。
推荐阅读
- 开学第一天(下)
- 20170612时间和注意力开销记录
- 深入理解Go之generate
- 开花店的前景怎么样()
- 眉头开了
- 上班后阅读开始变成一件奢侈的事
- 2020-04-07vue中Axios的封装和API接口的管理
- 小影写在2018九月开学季
- 标签、语法规范、内联框架、超链接、CSS的编写位置、CSS语法、开发工具、块和内联、常用选择器、后代元素选择器、伪类、伪元素。
- 从蓦然回首到花开在眼前,都是为了更好的明天。