zoukankan      html  css  js  c++  java
  • iOS 折线图实现

    图表绘制的过程实际上是坐标位置的计算过程,至于画线只要有了position,通过CAShapeLayer+BezierPath很快就可以画出来,这里提供一个绘制折线的demo,贵在思路,有需要的可以参考
    demo下载地址:https://github.com/qqcc1388/TYLineViewDemo

    话不多说,效果图和代码

    //单根折线实现

    #import <UIKit/UIKit.h>
    
    @interface TYLineView : UIView
    
    @property (nonatomic,strong) NSArray *datas;
    
    @property (nonatomic,strong) UIColor *lineColor;
    
    /**
     是否显示灰色背景
     */
    @property (nonatomic,assign) BOOL isShowBack;
    
    
    
    @end
    
    #import "TYLineView.h"
    
    #define kMarginX        30
    #define kMarginY        20
    
    @interface TYLineView  ()
    
    @property (nonatomic,strong)CAShapeLayer *shapeLayer;//划线layer
    
    @property (nonatomic,strong) CAShapeLayer *backLayer; //背景
    
    @property (nonatomic,assign)CGFloat  maxYvalue;     //最大y值
    
    @property (nonatomic,assign) NSInteger xAxisCount;  //x轴点数
    
    @property (nonatomic,assign) NSInteger yAxisCount;  //y轴点数
    
    @end
    
    @implementation TYLineView
    
    -(instancetype)initWithFrame:(CGRect)frame
    {
        if (self = [super initWithFrame:frame]) {
            
            [self initialize];
        }
        return self;
    }
    
    
    -(instancetype)initWithCoder:(NSCoder *)aDecoder
    {
        if (self = [super initWithCoder:aDecoder]) {
            
            [self initialize];
        }
        return self;
    }
    
    -(void)initialize
    {
        //闭合背景
        _backLayer = [[CAShapeLayer alloc] init];
        _backLayer.fillColor = [UIColor grayColor].CGColor;
        _backLayer.frame = self.bounds;
        [self.layer addSublayer:_backLayer];
        
        //主线段
        _shapeLayer = [[CAShapeLayer alloc] init];
        _shapeLayer.lineWidth = 1;
        _shapeLayer.lineCap = @"round";
        _shapeLayer.lineJoin = @"round";
        _shapeLayer.strokeColor = [UIColor redColor].CGColor;
        _shapeLayer.fillColor = [UIColor clearColor].CGColor;
        _shapeLayer.frame = self.bounds;
        [self.layer addSublayer:_shapeLayer];
        
        //初始化
        self.isShowBack = NO;
        self.yAxisCount = 5;
        self.backgroundColor = [UIColor cyanColor];
    }
    
    -(void)setDatas:(NSArray *)datas{
        //保存数据
        _datas = datas;
        
        //设置最大值
        self.maxYvalue = 200;
        //设置xAxisCount
        self.xAxisCount = datas.count;
        
        [self setNeedsDisplay];
        //划线
        [self drawLine];
    }
    
    -(void)drawLine
    {
        
        CGFloat totalHeight = CGRectGetHeight(self.frame) - kMarginY*2;
    //    CGFloat maxY = self.maxYvalue;
        CGFloat totoalWidth = CGRectGetWidth(self.frame) - kMarginX*2;
        //x轴每一段的宽度
        CGFloat perX = totoalWidth/(self.xAxisCount-1)*1.0;
        
        CGFloat yper = totalHeight/self.maxYvalue;  //y轴一个单位的高度
        //主线段曲线
        UIBezierPath *bezierPath = [UIBezierPath bezierPath];
        //背景曲线
        UIBezierPath *backPath = [UIBezierPath bezierPath];
        //原点
        CGPoint startPoint = CGPointMake(kMarginX,totalHeight + kMarginY);
        [backPath moveToPoint:startPoint];
        
        for (int i = 0; i < _datas.count; i++) {
            NSInteger valueY = [_datas[i] integerValue];
            CGFloat x = kMarginX + perX*i;
            CGFloat y = (totalHeight + kMarginY) - yper*valueY;
            CGPoint point = CGPointMake(x,y);
            if (i == 0) {
                [bezierPath moveToPoint:point];
            }else{
                [bezierPath addLineToPoint:point];
            }
            [backPath addLineToPoint:point];
        }
        //终点
        CGPoint endPoint = CGPointMake(kMarginX + perX*(self.datas.count-1), totalHeight + kMarginY);
        [backPath addLineToPoint:endPoint];
        
        //开始动画
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
        animation.duration = 2.0f;
        animation.removedOnCompletion = NO;
        animation.fillMode = kCAFillModeForwards;
        animation.fromValue = @(0);
        animation.toValue =@(1);
        self.shapeLayer.path = bezierPath.CGPath;
        [self.shapeLayer addAnimation:animation forKey:@"strokeEnd"];
        self.backLayer.path = backPath.CGPath;
    }
    
    
    
    -(void)drawRect:(CGRect)rect
    {
        [super drawRect:rect];
        UIBezierPath *path = [UIBezierPath bezierPath];
        [path setLineWidth:1.0f];
        [[UIColor redColor] set];
    
        CGFloat totalWidth = self.bounds.size.width;
        CGFloat totalHeight = self.bounds.size.height;
        
        //画坐标系
        //------> y轴
        [path moveToPoint:CGPointMake(kMarginX,kMarginY)];
        [path addLineToPoint:CGPointMake(kMarginX, totalHeight - kMarginY)];
        
        //------> x轴
        [path addLineToPoint:CGPointMake(totalWidth - kMarginX, totalHeight - kMarginY)];
        [path stroke];
    
        //线段 - y轴
        CGFloat perHeight = ((totalHeight - kMarginY*2)/(self.yAxisCount));
        for (int i = 0; i < self.yAxisCount; i++) {
            CGFloat y = perHeight*i + kMarginY;
    
            UIBezierPath *path = [UIBezierPath bezierPath];
            [path setLineWidth:1.0f];
            [[UIColor blueColor] set];
            [path moveToPoint:CGPointMake(kMarginX, y)];
            [path addLineToPoint:CGPointMake(kMarginX+ 5, y)];
            [path stroke];
        }
        
        //线段 - x轴
        CGFloat perWidth = (totalWidth - kMarginX*2)/(self.xAxisCount*1.0);
        for (int i = 0; i < self.xAxisCount; i++) {
            CGFloat x = perWidth*(i+1);
            CGFloat y = totalHeight - kMarginY;
            UIBezierPath *path = [UIBezierPath bezierPath];
            [path setLineWidth:1.0f];
            [[UIColor blueColor] set];
            [path moveToPoint:CGPointMake(x+kMarginX, y)];
            [path addLineToPoint:CGPointMake(x+kMarginX, y-5)];
            [path stroke];
        }
        
        //画y轴文字
        NSMutableArray *yArr = [NSMutableArray array];
    
        for (int i = 0; i < self.yAxisCount; i++) {
    
            [yArr  addObject:[NSString stringWithFormat:@"%.f",self.maxYvalue - self.maxYvalue/self.yAxisCount *i]];
        }
        [yArr addObject:@"0"];
        
        for (int i = 0; i < yArr.count ; i++) {
            NSString *title = yArr[i];
            CGFloat y = ((totalHeight - kMarginY*2)/(self.yAxisCount))*i + kMarginY;
            NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
            [style setAlignment:NSTextAlignmentCenter];
    
            [title drawInRect:CGRectMake(0,y-5, kMarginX, 20) withAttributes:@{NSForegroundColorAttributeName:[UIColor redColor],NSFontAttributeName:[UIFont systemFontOfSize:10],NSParagraphStyleAttributeName:style}];
        }
    
    }
    
    
    
    #pragma mark setter getter
    -(void)setLineColor:(UIColor *)lineColor{
        _lineColor = lineColor;
        self.shapeLayer.strokeColor = lineColor.CGColor;
    }
    
    -(void)setIsShowBack:(BOOL)isShowBack{
        _isShowBack = isShowBack;
        self.backLayer.hidden = !isShowBack;
    }
    
    
    

    多根线一起

    #import <UIKit/UIKit.h>
    
    @interface TYMultiLineView : UIView
    
    -(void)addLineWithDatas:(NSArray *)datas lineColor:(UIColor *)color animated:(BOOL)animated;
    
    @end
    
    #import "TYMultiLineView.h"
    
    #define kMarginX        30
    #define kMarginY        20
    
    @interface TYMultiLineView ()
    
    @property (nonatomic,assign)CGFloat  maxYvalue;     //最大y值
    
    @property (nonatomic,assign) NSInteger xAxisCount;  //x轴点数
    
    @property (nonatomic,assign) NSInteger yAxisCount;  //y轴点数
    
    @end
    
    @implementation TYMultiLineView
    
    -(instancetype)initWithFrame:(CGRect)frame
    {
        if (self = [super initWithFrame:frame]) {
            
            [self initialize];
        }
        return self;
    }
    
    -(instancetype)initWithCoder:(NSCoder *)aDecoder
    {
        if (self = [super initWithCoder:aDecoder]) {
            
            [self initialize];
        }
        return self;
    }
    
    -(void)initialize
    {
        self.backgroundColor = [UIColor cyanColor];
        
        self.maxYvalue = 200;
        self.yAxisCount = 5;
        self.xAxisCount = 5;
        
        [self setNeedsDisplay];
    }
    
    -(void)addLineWithDatas:(NSArray *)datas lineColor:(UIColor *)color animated:(BOOL)animated{
        
        //设置最大值
        self.maxYvalue = 200;
        //设置xAxisCount
        self.xAxisCount = datas.count;
        
        CAShapeLayer* lineLayer = [[CAShapeLayer alloc] init];
        lineLayer.lineWidth = 1;
        lineLayer.lineCap = @"round";
        lineLayer.lineJoin = @"round";
        lineLayer.strokeColor = color.CGColor;
        lineLayer.fillColor = [UIColor clearColor].CGColor;
        lineLayer.frame = self.bounds;
        [self.layer addSublayer:lineLayer];
        
        UIBezierPath *path = [self prepareBezierPathDatas:datas];
        
        lineLayer.path = path.CGPath;
        
        if(animated){
            CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
            animation.duration = 2.0f;
            animation.removedOnCompletion = NO;
            animation.fillMode = kCAFillModeForwards;
            animation.fromValue = @(0);
            animation.toValue =@(1);
            [lineLayer addAnimation:animation forKey:@"strokeEnd"];
        }
        
    }
    
    -(UIBezierPath *)prepareBezierPathDatas:(NSArray *)datas
    {
        
        CGFloat totalHeight = CGRectGetHeight(self.frame) - kMarginY*2;
        //    CGFloat maxY = self.maxYvalue;
        CGFloat totoalWidth = CGRectGetWidth(self.frame) - kMarginX*2;
        //x轴每一段的宽度
        CGFloat perX = totoalWidth/(self.xAxisCount-1)*1.0;
        
        CGFloat yper = totalHeight/self.maxYvalue;  //y轴一个单位的高度
        //主线段曲线
        UIBezierPath *bezierPath = [UIBezierPath bezierPath];
    
        
        for (int i = 0; i < datas.count; i++) {
            NSInteger valueY = [datas[i] integerValue];
            CGFloat x = kMarginX + perX*i;
            CGFloat y = (totalHeight + kMarginY) - yper*valueY;
            CGPoint point = CGPointMake(x,y);
            if (i == 0) {
                [bezierPath moveToPoint:point];
            }else{
                [bezierPath addLineToPoint:point];
            }
        }
        return bezierPath;
    }
    
    -(void)drawRect:(CGRect)rect
    {
        [super drawRect:rect];
        UIBezierPath *path = [UIBezierPath bezierPath];
        [path setLineWidth:1.0f];
        [[UIColor redColor] set];
        
        CGFloat totalWidth = self.bounds.size.width;
        CGFloat totalHeight = self.bounds.size.height;
        
        //画坐标系
        //------> y轴
        [path moveToPoint:CGPointMake(kMarginX,kMarginY)];
        [path addLineToPoint:CGPointMake(kMarginX, totalHeight - kMarginY)];
        
        //------> x轴
        [path addLineToPoint:CGPointMake(totalWidth - kMarginX, totalHeight - kMarginY)];
        [path stroke];
        
        //线段 - y轴
        CGFloat perHeight = ((totalHeight - kMarginY*2)/(self.yAxisCount));
        for (int i = 0; i < self.yAxisCount; i++) {
            CGFloat y = perHeight*i + kMarginY;
            
            UIBezierPath *path = [UIBezierPath bezierPath];
            [path setLineWidth:1.0f];
            [[UIColor blueColor] set];
            [path moveToPoint:CGPointMake(kMarginX, y)];
            [path addLineToPoint:CGPointMake(kMarginX+ 5, y)];
            [path stroke];
        }
        
        //线段 - x轴
        CGFloat perWidth = (totalWidth - kMarginX*2)/(self.xAxisCount*1.0);
        for (int i = 0; i < self.xAxisCount; i++) {
            CGFloat x = perWidth*(i+1);
            CGFloat y = totalHeight - kMarginY;
            UIBezierPath *path = [UIBezierPath bezierPath];
            [path setLineWidth:1.0f];
            [[UIColor blueColor] set];
            [path moveToPoint:CGPointMake(x+kMarginX, y)];
            [path addLineToPoint:CGPointMake(x+kMarginX, y-5)];
            [path stroke];
        }
        
        //画y轴文字
        NSMutableArray *yArr = [NSMutableArray array];
        
        for (int i = 0; i < self.yAxisCount; i++) {
            
            [yArr  addObject:[NSString stringWithFormat:@"%.f",self.maxYvalue - self.maxYvalue/self.yAxisCount *i]];
        }
        [yArr addObject:@"0"];
        
        for (int i = 0; i < yArr.count ; i++) {
            NSString *title = yArr[i];
            CGFloat y = ((totalHeight - kMarginY*2)/(self.yAxisCount))*i + kMarginY;
            NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
            [style setAlignment:NSTextAlignmentCenter];
            
            [title drawInRect:CGRectMake(0,y-5, kMarginX, 20) withAttributes:@{NSForegroundColorAttributeName:[UIColor redColor],NSFontAttributeName:[UIFont systemFontOfSize:10],NSParagraphStyleAttributeName:style}];
        }
        
    }
    
    

    使用方法

    lineView:
    [self.lineView setDatas:[self prepareDatas]];
    
    multiLineView:
        [self.multiLineView addLineWithDatas:[self prepareDatas] lineColor:[UIColor colorWithRed:arc4random_uniform(256)/255.0 green:arc4random_uniform(256)/255.0 blue:arc4random_uniform(256)/255.0 alpha:1] animated:YES];
    
    -(NSArray *)prepareDatas{
        NSMutableArray *datas = [NSMutableArray array];
        for (int i = 0; i < 5; i++) {
            [datas addObject:@(arc4random_uniform(201)).stringValue];
        }
        return datas;
    }
    
    

    关于动画画线方法:给shapelayer添加strokeEnd动画

            CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
            animation.duration = 2.0f;
            animation.removedOnCompletion = NO;
            animation.fillMode = kCAFillModeForwards;
            animation.fromValue = @(0);
            animation.toValue =@(1);
            [lineLayer addAnimation:animation forKey:@"strokeEnd"];
    

    demo中还有很多地方需要完善,这里仅仅是抛砖引玉提供一种实现的方案,细节的处理和其他的特殊需求请参考demo,自己进行拓展

  • 相关阅读:
    Conntect Bluetooth devices in iOS.
    Why NSAttributedString import html must be on main thread?
    IOS7 SDK 几宗罪
    How to browse the entire documentation using XCode 5 Documentation and API Reference ?
    High Precision Timers in iOS / OS X
    [UWP]抄抄《CSS 故障艺术》的动画
    [Microsoft Teams]使用连接器接收Azure DevOps的通知
    [WPF 自定义控件]自定义一个“传统”的 Validation.ErrorTemplate
    [WPF 自定义控件]在MenuItem上使用RadioButton
    [WPF 自定义控件]创建包含CheckBox的ListBoxItem
  • 原文地址:https://www.cnblogs.com/qqcc1388/p/7359545.html
Copyright © 2011-2022 走看看