绘图-几种基本统计图的实现分析

绘图-几种基本统计图的实现分析_第1张图片

前言

在开发中我们会遇到各种统计图,或者各种绘图,本文通过对基本三大统计图:折线图、柱状图、扇形图的实现来掌握基本统计图的绘制,在下一篇文中会带来复杂一些的绘图案例分析,循序渐进达、触类旁通达到绘制各式各样图表的能力。


折线图

绘图-几种基本统计图的实现分析_第2张图片
折线图.gif

通过自定义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:@""];
     }
    

    ** 绘制虚线**

绘图-几种基本统计图的实现分析_第3张图片
绘制虚线
 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, 2
    M_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}]

绘图-几种基本统计图的实现分析_第4张图片

避免出现上图上的效果图,两种方法:

  • 每次addLineToPoint 后 moveToPoint

    [progressline addLineToPoint:point];
    [progressline moveToPoint:point];
    
  • 设置 CAShapeLayer 的fillColor 为 clearColor

    _chartLine.fillColor   = [[UIColor clearColor] CGColor];
    

柱状图

绘图-几种基本统计图的实现分析_第5张图片
柱状图.gif

自定义 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"];

扇形图

绘图-几种基本统计图的实现分析_第6张图片
使用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));
      参数:画布,x,y为圆点坐标,radius半径,startAngle为开始的弧度,endAngle为 结束的弧度,clockwise 0为顺时针,1为逆时针。
      CGContextAddArc(ctx, 160, 300, 100,  0, allAngle*0.3, 0);
      CGContextFillPath(ctx);  
    }
    
  • 使用UIBezierPath绘制扇形

    绘图-几种基本统计图的实现分析_第7张图片
    使用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];
    }

绘图-几种基本统计图的实现分析_第8张图片
动态扇形图.gif
  • 使用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];
    

小结

考虑到篇幅,这篇文就只介绍折线、柱状、扇形这三大基本统计图的绘制,原理都是一样的,只是需要一些思路和技巧,下篇会带来一些复杂些的绘图案例分析。

你可能感兴趣的:(绘图-几种基本统计图的实现分析)