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

前言

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


折线图

折线图.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:@""];
     }
    

    ** 绘制虚线**

绘制虚线
 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}]

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

  • 每次addLineToPoint 后 moveToPoint

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

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

柱状图

柱状图.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"];

扇形图

使用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绘制扇形

    使用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];
    }

动态扇形图.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];
    

小结

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容