前言
在開發(fā)中我們會(huì)遇到各種統(tǒng)計(jì)圖,或者各種繪圖,本文通過對(duì)基本三大統(tǒng)計(jì)圖:折線圖、柱狀圖、扇形圖的實(shí)現(xiàn)來(lái)掌握基本統(tǒng)計(jì)圖的繪制,在下一篇文中會(huì)帶來(lái)復(fù)雜一些的繪圖案例分析,循序漸進(jìn)達(dá)、觸類旁通達(dá)到繪制各式各樣圖表的能力。
折線圖
通過自定義UIView使用自定義init方法賦值數(shù)據(jù)源,后調(diào)用 UIView的drawRect方法進(jìn)行繪制。
重繪的時(shí)候 [self setNeedsDisplay]; 會(huì)自動(dòng)調(diào)用 drawRect 方法。
繪制折線的時(shí)候最基本的是繪制直線、繪制圓點(diǎn)、繪制數(shù)據(jù)
-
繪制線段
使用Core Graphics
context為drawRect 方法中獲取的。
CGContextRef context = UIGraphicsGetCurrentContext();//畫線共用方法。畫直線 坐標(biāo)軸、橫豎線、連接線 - (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,可以實(shí)現(xiàn)動(dòng)態(tài)繪制的動(dòng)畫效果。
使用for循環(huán)繪制多條折線的步驟(for 循環(huán)一次的情況下): 初始化一個(gè) CAShapeLayer ,加載在 當(dāng)前的layer上。 初始化 UIBezierPath 供CAShapeLayer 使用; 使用 for循環(huán)再繪制余下的每一個(gè)圓點(diǎn),確保每一個(gè)圓點(diǎn)都在 CAShapeLayer 的上層, 同時(shí)對(duì)UIBezierPath添加每一個(gè)余下的點(diǎn)路徑,這樣就不會(huì)在繪制折線的時(shí)候,影響到圓點(diǎn)的展示。 使用CABasicAnimation 利用layer 的strokeEnd屬性動(dòng)態(tài)繪制,不使用動(dòng)畫時(shí),會(huì)直接一下繪制完成。 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 繪制圓點(diǎn) 拼接路徑 moveToPoint 設(shè)置起點(diǎn) [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]; 繪制圓點(diǎn) 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設(shè)置 虛線寬,線間距 數(shù)組第一個(gè)是虛線中實(shí)現(xiàn)的長(zhǎng)度,第二個(gè)是虛線中空白的寬度。
設(shè)置一個(gè) UIBezierPath 繪制好路徑賦值給 CAShapeLayer即可。
[shapeLayer setLineDashPattern:[NSArray arrayWithObjects:[NSNumber numberWithInt:5], [NSNumber numberWithInt:5], nil]];
繪制圓點(diǎn)
使用Core Graphics
UIColoraColor = [UIColor colorWithRed:0.17 green:0.67 blue:0.25 alpha:1.00]; //點(diǎn)的顏色
CGContextSetFillColorWithColor(context, aColor.CGColor);//填充顏色
CGContextAddArc(context, startPoint.x, startPoint.y, 3, 0, 2M_PI, 0); //添加一個(gè)圓
CGContextDrawPath(context, kCGPathFill);//繪制填充-
繪制數(shù)據(jù)
** 在繪制數(shù)據(jù)這一塊,如果值很多,大量的數(shù)據(jù)使用UILabel是不合適的,不但造成資源耗費(fèi),而且數(shù)據(jù)多橫向拉動(dòng)的話會(huì)造成卡頓。推薦使用:**[title drawInRect:titleRect withAttributes:@{NSFontAttributeName :[UIFont systemFontOfSize:8],NSForegroundColorAttributeName:kChartTextColor}];
** 值得注意的是,使用string 的drawInRect 繪制時(shí),只要字體大小、高度合適字體會(huì)自動(dòng)換行,當(dāng)然也可以 拼接/n 就可以達(dá)到換行的效果了。如果需要設(shè)置字體排版(如居中)**
NSMutableParagraphStyle * paragraph = [[NSMutableParagraphStyle alloc]init];
paragraph.alignment = NSTextAlignmentRight;
xxxx withAttributes:@{NSParagraphStyleAttributeName:paragraph}]
避免出現(xiàn)上圖上的效果圖,兩種方法:
-
每次addLineToPoint 后 moveToPoint
[progressline addLineToPoint:point]; [progressline moveToPoint:point];
-
設(shè)置 CAShapeLayer 的fillColor 為 clearColor
_chartLine.fillColor = [[UIColor clearColor] CGColor];
柱狀圖
自定義 UUBar類,展示的是單個(gè)柱狀的效果,在 UUBarChart類中調(diào)用生成多個(gè)柱狀的效果。
UUBar中 使用CAShapeLayer 、UIBezierPath、CABasicAnimation可實(shí)現(xiàn)動(dòng)態(tài)柱狀圖
CAShapeLayer設(shè)置
_chartLine.fillColor = [[UIColor whiteColor] CGColor];
_chartLine.lineWidth = self.frame.size.width;
路線設(shè)置
UIBezierPath *progressline = [UIBezierPath bezierPath];
起點(diǎn)
[progressline moveToPoint:CGPointMake(self.frame.size.width/2.0, self.frame.size.height+30)];
終點(diǎn)
[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];
}
動(dòng)畫設(shè)置
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)); 參數(shù):畫布,x,y為圓點(diǎn)坐標(biāo),radius半徑,startAngle為開始的弧度,endAngle為 結(jié)束的弧度,clockwise 0為順時(shí)針,1為逆時(shí)針。 CGContextAddArc(ctx, 160, 300, 100, 0, allAngle*0.3, 0); CGContextFillPath(ctx); }
-
使用UIBezierPath繪制扇形
使用UIBezierPath繪制扇形在我這篇文章中我說(shuō)過:UIBezierPath是在 UIKit 中的一個(gè)類,繼承于NSObject,可以創(chuàng)建基于矢量的路徑.此類是Core Graphics框架關(guān)于path的一個(gè)OC封裝。所以 UIBezierPath 是基于 Core Graphics 實(shí)現(xiàn)的一項(xiàng)繪圖技術(shù)。所以使用UIBezierPath當(dāng)然也是可以繪制圖形的,只是必須在 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實(shí)現(xiàn)動(dòng)態(tài)扇形
使用strokeColor
CAShapeLayer *circle = [CAShapeLayer layer];CGPoint center = CGPointMake(160, 300); CGFloat radius = 50; //這里的思路是只設(shè)置一條路徑供所有的CAShapeLayer使用,實(shí)際上 當(dāng)前這條 //UIBezierPath 畫的是一個(gè)圓,控制每個(gè)CAShapeLayer 的strokeStart和strokeEnd 即可定區(qū)域繪制了 UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:-M_PI_2 endAngle:M_PI_2*3 clockwise:YES]; //fillColor必須設(shè)置為clearColor circle.fillColor = [UIColor clearColor].CGColor; circle.strokeColor = [UIColor magentaColor].CGColor; //lineWidth必須設(shè)置為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]; 設(shè)定一個(gè)起點(diǎn) [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];
小結(jié)
考慮到篇幅,這篇文就只介紹折線、柱狀、扇形這三大基本統(tǒng)計(jì)圖的繪制,原理都是一樣的,只是需要一些思路和技巧,下篇會(huì)帶來(lái)一些復(fù)雜些的繪圖案例分析。