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

前言

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


折线图

折线图.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);
  }

作者:進无尽
链接:https://www.jianshu.com/p/a3b8fbb00170
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

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

作者:進无尽
链接:https://www.jianshu.com/p/a3b8fbb00170
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

** 绘制虚线**

绘制虚线

 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];

柱状图

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

小结

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏老司机的简书

老司机带你走进Core Animation 之图层的透视、渐变及复制

老司机的想法就是要把CoreAnimation头文件中的类大概都说一遍,毕竟一开始把系列名定成了《老司机带你走进CoreAnimation》(深切的觉得自己给自...

1614
来自专栏iOSer成长记录

iOS-自定义button详解

1203
来自专栏進无尽的文章

动画| 金币抛入红包动画详解

这个动画效果很早就出来了,也是一个比较经典的关键帧动画和组合动画的运用,通过剖析源码,可以发现实际上这个酷炫的动画实现起来很简单。

3085
来自专栏算法+

pixel art之 hqx 算法

在去年的时候,偶然看到hqx算法。 一个高质量的插值放大算法。 与双线性插值等插值算法相比,这个算法放大后对人眼保护相对比较好。 没有双线性插值看起来模糊,固然...

3616
来自专栏進无尽的文章

动画| 3D空间变幻之CATransform3D的使用

CGAffineTransform(仿射变换)是作用于UIViews的2D操作,而CATransform3D是作用于CALayers的更复杂的3D操作,这两种变...

1621
来自专栏陈满iOS

iOS动画专题·UIView二维形变动画与CAAnimation核心动画(transform动画,基础,关键帧,组动画,路径动画,贝塞尔曲线)

总的来说,从涉及类的形式来看,iOS动画有:基于UIView的仿射形变动画,基于CAAnimation及其子类的动画,基于CG的动画。这篇文章着重总结前两种动画...

4211
来自专栏非典型技术宅

iOS动画系列之八:使用CAShapeLayer绘画动态流量图1. CAShapeLayer2. 实战:绘制一个镂空图层动画3. 使用CAShapeLayer绘画动态流量图

2183
来自专栏向治洪

React Native仿美团下拉菜单

在很多产品中都会涉及到下拉菜单选择功能,用的最好的当属美团了,其效果如下: ? 要实现上面的效果,在原生中比较好做,直接使用PopWindow组件即可。如...

52910
来自专栏Java成神之路

JavaUtil_03_图片处理工具类

1674
来自专栏前端儿

Windows下编程--模拟时钟的实现

(4)   编写显示数字时钟函数。注意要自己用矩形填充(FillRect)擦除背景。

1241

扫码关注云+社区