前言
在开发中我们会遇到各种统计图,或者各种绘图,本文通过对基本三大统计图:折线图、柱状图、扇形图的实现来掌握基本统计图的绘制,在下一篇文中会带来复杂一些的绘图案例分析,循序渐进达、触类旁通达到绘制各式各样图表的能力。
折线图
通过自定义UIView使用自定义init方法赋值数据源,后调用 UIView的drawRect方法进行绘制。
重绘的时候 [self setNeedsDisplay]; 会自动调用 drawRect 方法。
绘制折线的时候最基本的是绘制直线、绘制圆点、绘制数据
-
绘制线段
使用Core Graphics
context为drawRect 方法中获取的。
CGContextRef context = UIGraphicsGetCurrentContext();//画线共用方法。画直线 坐标轴、横竖线、连接线 - (void)drawLine:(CGContextRef)context startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint lineColor:(UIColor *)lineColor lineWidth:(CGFloat)width { CGContextSetShouldAntialias(context, YES ); //抗锯齿 CGColorSpaceRef Linecolorspace1 = CGColorSpaceCreateDeviceRGB(); CGContextSetStrokeColorSpace(context, Linecolorspace1); CGContextSetLineWidth(context, width); CGContextSetStrokeColorWithColor(context, lineColor.CGColor); CGContextMoveToPoint(context, startPoint.x, startPoint.y); CGContextAddLineToPoint(context, endPoint.x, endPoint.y); CGContextStrokePath(context); CGColorSpaceRelease(Linecolorspace1); }
使用CAShapeLayer 和 UIBezierPath,可以实现动态绘制的动画效果。
使用for循环绘制多条折线的步骤(for 循环一次的情况下): 初始化一个 CAShapeLayer ,加载在 当前的layer上。 初始化 UIBezierPath 供CAShapeLayer 使用; 使用 for循环再绘制余下的每一个圆点,确保每一个圆点都在 CAShapeLayer 的上层, 同时对UIBezierPath添加每一个余下的点路径,这样就不会在绘制折线的时候,影响到圆点的展示。 使用CABasicAnimation 利用layer 的strokeEnd属性动态绘制,不使用动画时,会直接一下绘制完成。 for (int i=0; i<_yValues.count; i++) { //划线 CAShapeLayer *_chartLine = [CAShapeLayer layer]; _chartLine.lineCap = kCALineCapRound; _chartLine.lineJoin = kCALineJoinBevel; _chartLine.fillColor = [[UIColor whiteColor] CGColor]; _chartLine.lineWidth = 2.0; [self.layer addSublayer:_chartLine]; UIBezierPath *progressline = [UIBezierPath bezierPath]; CGFloat firstValue = [[childAry objectAtIndex:0] floatValue]; CGFloat xPosition = (UUYLabelwidth + _xLabelWidth/2.0); CGFloat chartCavanHeight = self.frame.size.height - UULabelHeight*3 绘制圆点 拼接路径 moveToPoint 设置起点 [progressline moveToPoint:CGPointMake(xPosition, chartCavanHeight - grade * chartCavanHeight+UULabelHeight)]; [progressline setLineWidth:2.0]; [progressline setLineCapStyle:kCGLineCapRound]; [progressline setLineJoinStyle:kCGLineJoinRound]; NSInteger index = 0; for (NSString * valueString in childAry) { float grade =([valueString floatValue]-_yValueMin) / ((float)_yValueMax-_yValueMin); CGPoint point = CGPointMake(xPosition+index*_xLabelWidth, chartCavanHeight - grade * chartCavanHeight+UULabelHeight); 拼接路径 [progressline addLineToPoint:point]; 绘制圆点 index += 1; } _chartLine.path = progressline.CGPath; _chartLine.strokeColor = [UUGreen CGColor]; CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; pathAnimation.duration = childAry.count*0.4; pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f]; pathAnimation.toValue = [NSNumber numberWithFloat:1.0f]; [_chartLine addAnimation:pathAnimation forKey:@""]; }
** 绘制虚线**
CAShapeLayer设置 虚线宽,线间距 数组第一个是虚线中实现的长度,第二个是虚线中空白的宽度。
设置一个 UIBezierPath 绘制好路径赋值给 CAShapeLayer即可。
[shapeLayer setLineDashPattern:[NSArray arrayWithObjects:[NSNumber numberWithInt:5], [NSNumber numberWithInt:5], nil]];
绘制圆点
使用Core Graphics
UIColoraColor = [UIColor colorWithRed:0.17 green:0.67 blue:0.25 alpha:1.00]; //点的颜色
CGContextSetFillColorWithColor(context, aColor.CGColor);//填充颜色
CGContextAddArc(context, startPoint.x, startPoint.y, 3, 0, 2M_PI, 0); //添加一个圆
CGContextDrawPath(context, kCGPathFill);//绘制填充-
绘制数据
** 在绘制数据这一块,如果值很多,大量的数据使用UILabel是不合适的,不但造成资源耗费,而且数据多横向拉动的话会造成卡顿。推荐使用:**[title drawInRect:titleRect withAttributes:@{NSFontAttributeName :[UIFont systemFontOfSize:8],NSForegroundColorAttributeName:kChartTextColor}];
** 值得注意的是,使用string 的drawInRect 绘制时,只要字体大小、高度合适字体会自动换行,当然也可以 拼接/n 就可以达到换行的效果了。如果需要设置字体排版(如居中)**
NSMutableParagraphStyle * paragraph = [[NSMutableParagraphStyle alloc]init];
paragraph.alignment = NSTextAlignmentRight;
xxxx withAttributes:@{NSParagraphStyleAttributeName:paragraph}]
避免出现上图上的效果图,两种方法:
-
每次addLineToPoint 后 moveToPoint
[progressline addLineToPoint:point]; [progressline moveToPoint:point];
-
设置 CAShapeLayer 的fillColor 为 clearColor
_chartLine.fillColor = [[UIColor clearColor] CGColor];
柱状图
自定义 UUBar类,展示的是单个柱状的效果,在 UUBarChart类中调用生成多个柱状的效果。
UUBar中 使用CAShapeLayer 、UIBezierPath、CABasicAnimation可实现动态柱状图
CAShapeLayer设置
_chartLine.fillColor = [[UIColor whiteColor] CGColor];
_chartLine.lineWidth = self.frame.size.width;
路线设置
UIBezierPath *progressline = [UIBezierPath bezierPath];
起点
[progressline moveToPoint:CGPointMake(self.frame.size.width/2.0, self.frame.size.height+30)];
终点
[progressline addLineToPoint:CGPointMake(self.frame.size.width/2.0, (1 - grade) * self.frame.size.height+15)];
[progressline setLineWidth:1.0];
[progressline setLineCapStyle:kCGLineCapSquare];
_chartLine.path = progressline.CGPath;
if (_barColor) {
_chartLine.strokeColor = [_barColor CGColor];
}else{
_chartLine.strokeColor = [UUGreen CGColor];
}
动画设置
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
pathAnimation.duration = 1.5;
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
pathAnimation.toValue = [NSNumber numberWithFloat:1.0f];
pathAnimation.autoreverses = NO;
[_chartLine addAnimation:pathAnimation forKey:@"strokeEndAnimation"];
扇形图
-
使用Core Graphics绘制扇形
- (void)drawRect:(CGRect)rect { 一圆周的弧度(360度) CGFloat allAngle = M_PI*2; CGContextRef ctx = UIGraphicsGetCurrentContext(); CGContextMoveToPoint(ctx, 160, 300); CGContextSetFillColor(ctx, CGColorGetComponents( [UIColor redColor].CGColor)); 参数:画布,x,y为圆点坐标,radius半径,startAngle为开始的弧度,endAngle为 结束的弧度,clockwise 0为顺时针,1为逆时针。 CGContextAddArc(ctx, 160, 300, 100, 0, allAngle*0.3, 0); CGContextFillPath(ctx); }
-
使用UIBezierPath绘制扇形
在我这篇文章中我说过:UIBezierPath是在 UIKit 中的一个类,继承于NSObject,可以创建基于矢量的路径.此类是Core Graphics框架关于path的一个OC封装。所以 UIBezierPath 是基于 Core Graphics 实现的一项绘图技术。所以使用UIBezierPath当然也是可以绘制图形的,只是必须在 drawRect 方法中,不可在其他位置。
- (void)drawRect:(CGRect)rect
{
UIBezierPath *arcPath = [UIBezierPath bezierPath];
[arcPath moveToPoint:CGPointMake(160, 200)];
[arcPath addArcWithCenter:CGPointMake(160, 200) radius:50 startAngle:0 endAngle:M_PI * 0.3 clockwise:YES];
[[UIColor brownColor] set];
[arcPath fill];
[arcPath stroke];
}
-
使用CAShapeLayer 、UIBezierPath、CABasicAnimation实现动态扇形
使用strokeColor
CAShapeLayer *circle = [CAShapeLayer layer];CGPoint center = CGPointMake(160, 300); CGFloat radius = 50; //这里的思路是只设置一条路径供所有的CAShapeLayer使用,实际上 当前这条 //UIBezierPath 画的是一个圆,控制每个CAShapeLayer 的strokeStart和strokeEnd 即可定区域绘制了 UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:-M_PI_2 endAngle:M_PI_2*3 clockwise:YES]; //fillColor必须设置为clearColor circle.fillColor = [UIColor clearColor].CGColor; circle.strokeColor = [UIColor magentaColor].CGColor; //lineWidth必须设置为radius的2倍 circle.lineWidth = radius*2; circle.strokeStart = 0; circle.strokeEnd = 0.6; circle.path = path.CGPath; [self.view.layer addSublayer:circle]; CAShapeLayer *circle1 = [CAShapeLayer layer]; circle1.fillColor = [UIColor clearColor].CGColor; circle1.strokeColor = [UIColor purpleColor].CGColor; circle1.lineWidth = radius*2; circle1.strokeStart = 0.6; circle1.strokeEnd = 0.8; circle1.path = path.CGPath; [self autoDraw:circle :0 :0.6 :1]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self.view.layer addSublayer:circle1]; [self autoDraw:circle1 :0.6 :0.8 :0.5]; }); - (void)autoDraw :(CALayer *)layer :(CGFloat)x :(CGFloat)y :(CGFloat)duration { CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; animation.duration = duration; animation.fromValue = [NSNumber numberWithFloat:x]; animation.toValue = [NSNumber numberWithFloat:y];; animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; [layer addAnimation:animation forKey:@"circleAnimation"]; }
使用 fillColor
UIBezierPath *path = [UIBezierPath bezierPath]; 设定一个起点 [path moveToPoint:center]; 画圆弧 M_PI_2 90度,从水平右边开始 [path addArcWithCenter:center radius:radius startAngle:-M_PI_2 endAngle:M_PI_2*0.3 clockwise:YES]; circle.fillColor = [UIColor magentaColor].CGColor; circle.strokeColor = [UIColor clearColor].CGColor; circle.strokeStart = 0; circle.strokeEnd = 0.6; circle.path = path.CGPath; [self.view.layer addSublayer:circle];
小结
考虑到篇幅,这篇文就只介绍折线、柱状、扇形这三大基本统计图的绘制,原理都是一样的,只是需要一些思路和技巧,下篇会带来一些复杂些的绘图案例分析。