繪圖-幾種基本統(tǒng)計(jì)圖的實(shí)現(xiàn)分析

前言

在開發(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á)到繪制各式各樣圖表的能力。


折線圖

折線圖.gif

通過自定義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, 2
    M_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];
    

柱狀圖

柱狀圖.gif

自定義 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繪制扇形
  • 使用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];
    }

動(dòng)態(tài)扇形圖.gif
  • 使用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ù)雜些的繪圖案例分析。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容