在坐标系中绘制数据曲线

昨晚睡觉时qq聊天中看见别人的截图,截图内容为健康类app运动数据的展示截图,在坐标系中展示曲线数据,感觉很炫,今天自己动手写了一下,JDataCurveView封装了一下。
JDataCurveView头文件如下:

@interface JDataCurveView : UIView

//如果显示数据需经常刷新数据,值该值为yes,否则no
@property(nonatomic,assign)BOOL isDynamicRefreshData;
//Y坐标轴名称
@property(nonatomic,copy)NSString* yCoordinateName;
//Y坐标轴的单位名称
@property(nonatomic,copy)NSString* yCoordinateUnitName;
//X坐标轴名称
@property(nonatomic,copy)NSString* xCoordinateName;
//Y坐标轴的单位名称
@property(nonatomic,copy)NSString* xCoordinateUnitName;

//曲线条颜色
@property(nonatomic,strong)UIColor *curveColor;
//单位名称显示颜色
@property(nonatomic,strong)UIColor* unitNameColor;
//单位数据显示的颜色
@property(nonatomic,strong)UIColor* unitDataDisplayColor;
//坐标轴颜色
@property(nonatomic,strong)UIColor* coordinateColor;

//y轴显示数据
@property(nonatomic,strong)NSArray *yDisplayData;
//x轴显示数据
@property(nonatomic,strong)NSArray *xDisplayData;
//中间曲线数据
@property(nonatomic,strong)NSArray *dataArrary;

//是否填充曲线下部分内容
@property(nonatomic,assign)BOOL isNeedFill;
//填充曲线下部分内容颜色值
@property(nonatomic,strong)UIColor *fillColor;

@property(nonatomic,assign)CGFloat xMaxData;
@property(nonatomic,assign)CGFloat yMaxData;

@property(nonatomic,assign)BOOL isNeedDisplayXData;
@property(nonatomic,assign)BOOL isNeedDisplayYData;

+(JDataCurveView*)dataCurveView;
-(void)resetDataArray:(NSArray *)dataArrary;

@end

JDataCurveView源文件,具体如下:

#import "JDataCurveView.h"

@interface JDataCurveView ()

@property(nonatomic,strong)UIImage *coordinateBackgroundImg;
@property(nonatomic,strong)UIImage *dataDispalyImg;

@property(nonatomic,strong)UIImageView *coordinateBackgroundImgView;
@property(nonatomic,strong)UIImageView *dataDispalyImgView;

@property(nonatomic,strong)dispatch_queue_t queue;

@end

@implementation JDataCurveView

+(JDataCurveView*)dataCurveView
{
    return [[JDataCurveView alloc] init];
}

-(id)init
{
    if (self = [super init]) {
        [self setupUI];
    }
    return self;
}

-(void)setupUI
{
    _isNeedDisplayXData = YES;
    _isNeedDisplayYData = YES;
    _coordinateBackgroundImgView = [[UIImageView alloc] init];
    [self addSubview:_coordinateBackgroundImgView];
    
    _dataDispalyImgView = [[UIImageView alloc] init];
    [self addSubview:_dataDispalyImgView];
}

-(void)layoutSubviews
{
    _coordinateBackgroundImgView.frame = self.bounds;
    _dataDispalyImgView.frame = CGRectMake(40 + 1, 35 + 10, self.bounds.size.width - 30 - 41 - 10, self.bounds.size.height - 50 - 35 - 1 - 10);
}

drawRect绘画代码如下,采取后台线程绘制方法,避免阻塞主线程:

-(void)drawRect:(CGRect)rect
{
    if (!_coordinateBackgroundImg) {
        [self drawCoordinateBackgroundImg];
    }
    if (_isDynamicRefreshData) {
        [self drawDataImg];
    }
    else
    {
        if (!_dataDispalyImg) {
            [self drawDataImg];
        }
    }
}

drawCoordinateBackgroundImg的具体实现:

-(void)drawCoordinateBackgroundImg
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        UIGraphicsBeginImageContext(self.bounds.size);
        CGContextRef context = UIGraphicsGetCurrentContext();
        
        if (!_unitNameColor) {
            _unitNameColor = [UIColor grayColor];
        }
        CGContextSetRGBFillColor(context, 1, 0, 0, 1);

        if (!_yCoordinateName) {
            _yCoordinateName = @"Y轴";
        }
        
        NSString *yCoordinateName = [NSString stringWithFormat:@"%@:%@",_yCoordinateName,_yCoordinateUnitName?_yCoordinateUnitName:@""];
        [yCoordinateName drawAtPoint:CGPointMake(10,10) withAttributes:@{NSForegroundColorAttributeName:_unitNameColor}];
        
        if (!_xCoordinateName) {
            _xCoordinateName = @"X轴";
        }
        NSString *xCoordinateName = [NSString stringWithFormat:@"%@:%@",_xCoordinateName,_xCoordinateUnitName?_xCoordinateUnitName:@""];
        [xCoordinateName drawAtPoint:CGPointMake(self.bounds.size.width - 30 - 25, self.bounds.size.height - 20) withAttributes:@{NSForegroundColorAttributeName:_unitNameColor}];
        
        
        if (!_coordinateColor) {
            _coordinateColor = [UIColor grayColor];
        }
        
        CGContextSetStrokeColorWithColor(context, _coordinateColor.CGColor);
        UIBezierPath *bezierPath = [UIBezierPath bezierPath];
        //画坐标系
        [bezierPath moveToPoint:CGPointMake(40, 35)];
        [bezierPath addLineToPoint:CGPointMake(40, self.bounds.size.height - 50)];
        [bezierPath addLineToPoint:CGPointMake(self.bounds.size.width - 30, self.bounds.size.height - 50)];
        
        if (!_unitDataDisplayColor) {
            _unitDataDisplayColor = [UIColor whiteColor];
        }
        //画Y坐标刻度
        if (_yDisplayData && _yDisplayData.count > 0) {
            
            CGFloat yMaxData = [_yDisplayData[_yDisplayData.count - 1] floatValue];
            CGFloat yMaxLength = self.bounds.size.height - 35 - 50 - 10;
            CGFloat orgY = self.bounds.size.height - 50;
            
            for (NSNumber *yNum in _yDisplayData) {
                CGFloat length = yMaxLength * ([yNum floatValue] / yMaxData);
                if ([yNum floatValue] != 0) {
                    CGContextMoveToPoint(context, 40, orgY - length);
                    CGContextAddLineToPoint(context, 45, orgY - length);
                }
                
                if (_isNeedDisplayYData) {
                    NSString *dataStr = [NSString stringWithFormat:@"%@",yNum];
                    [dataStr drawAtPoint:CGPointMake(10, orgY - length - 10) withAttributes:@{NSForegroundColorAttributeName:_unitDataDisplayColor}];
                }
               
            }
        }
       
        //画X坐标刻度
        if (_xDisplayData && _xDisplayData.count > 0) {
            
            CGFloat xMaxData = [_xDisplayData[_xDisplayData.count - 1] floatValue];
            CGFloat xMaxLength = self.bounds.size.width - 40 - 30 - 10;
            CGFloat orgX = 40;
            
            for (NSNumber *xNum in _xDisplayData) {
                CGFloat length = xMaxLength * ([xNum floatValue] / xMaxData);
                if ([xNum floatValue] != 0) {
                    CGContextMoveToPoint(context,orgX + length, self.bounds.size.height - 50);
                    CGContextAddLineToPoint(context, orgX + length, self.bounds.size.height - 50 - 5);
                }
                
                if (_isNeedDisplayXData) {
                    NSString *dataStr = [NSString stringWithFormat:@"%@",xNum];
                    [dataStr drawAtPoint:CGPointMake(orgX + length - 8, self.bounds.size.height - 50 + 5) withAttributes:@{NSForegroundColorAttributeName:_unitDataDisplayColor}];
                }
            }
        }

        CGContextAddPath(context, bezierPath.CGPath);
        CGContextStrokePath(context);
        _coordinateBackgroundImg  = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        
        dispatch_async(dispatch_get_main_queue(), ^{
            _coordinateBackgroundImgView.image = _coordinateBackgroundImg;
        });
        
    });
}

drawDataImg实现:

-(void)drawDataImg
{
    if (!_dataArrary || _dataArrary.count <=0) {
        return;
    }
    
    dispatch_queue_t queue = NULL;

    if (_isDynamicRefreshData) {
        //创建串行队列,保证一张图片一张图片的显示,不乱

  //创建串行队列
        if(!_queue)
        {
            _queue = dispatch_queue_create("com.draw.dataImg", DISPATCH_QUEUE_SERIAL);

        }
        queue = _queue;
    }
    else
    {
        queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    }
    
    dispatch_async(queue, ^{
        
        UIGraphicsBeginImageContext(_dataDispalyImgView.frame.size);
        CGContextRef context = UIGraphicsGetCurrentContext();
        UIBezierPath *bezierPath = [UIBezierPath bezierPath];
        
        CGPoint point = [_dataArrary[0] CGPointValue];
        
        CGFloat xLength = _dataDispalyImgView.frame.size.width;
        CGFloat yLength = _dataDispalyImgView.frame.size.height;
        CGFloat xValue = point.x;
        CGFloat yValue = point.y;
        
        [[UIColor clearColor] setFill];
        if (!_curveColor) {
            _curveColor = [UIColor whiteColor];
        }
        [_curveColor setStroke];
        CGContextFillEllipseInRect(context, _dataDispalyImgView.bounds);
        
        [bezierPath moveToPoint:CGPointMake((xValue / _xMaxData) * xLength, yLength - (yValue / _yMaxData ) * yLength)];
        
        for (NSValue *value in _dataArrary)
        {
            point = [value CGPointValue];
            xValue = point.x;
            yValue = point.y;

            [bezierPath addLineToPoint:CGPointMake((xValue / _xMaxData) * xLength, yLength - (yValue / _yMaxData ) * yLength)];
            
        }
        CGContextAddPath(context, bezierPath.CGPath);
        CGContextDrawPath(context, kCGPathFillStroke);
        CGContextFillPath(context);
        _dataDispalyImg = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();

        dispatch_async(dispatch_get_main_queue(), ^{
            _dataDispalyImgView.image = _dataDispalyImg;
        });
        
    });
}

重新设置数据源:

-(void)resetDataArray:(NSArray *)dataArrary
{
    if (_isDynamicRefreshData) {
        _dataArrary = dataArrary;
        [self drawDataImg];
    }
}

回头看看实现的思路,不过先看看截图就好说一点:

屏幕快照 .png

这个绘画总共有两张图片构成,整的背景图片为整个坐标系,因为整个坐标系不会变化,只要绘制一次就可以,宁外红色部分是数据展示部分,因为数据部分可能改变,如显示人的脉搏跳动数据 实时改变,所以实时绘制,因此创建了一个串行队列,在串行队列中绘制,保证数据会被一张一张的绘制,一张一张的显示,不会乱。
现在看看使用方式:

-(void)testDataCurveView1
{
    //随机参数数据,模拟情况
    NSMutableArray *dataArray = [NSMutableArray array];
    for( float i = 0; i<= 70 ;i+=1)
    {
        CGFloat value = arc4random() % 201;
        CGPoint point = CGPointMake(i, value);
        [dataArray addObject:[NSValue valueWithCGPoint:point]];
    }
    
    JDataCurveView *dataCurveView = [JDataCurveView dataCurveView];
    dataCurveView.frame = CGRectMake(10, 64, self.view.frame.size.width - 20, 250);
    dataCurveView.yCoordinateName = @"海拔";
    dataCurveView.yCoordinateUnitName = @"米";
    dataCurveView.xCoordinateName = @"时间";
    dataCurveView.xCoordinateUnitName = @"分";
    dataCurveView.yDisplayData = @[@0,@50,@100,@150,@200];
    dataCurveView.xDisplayData = @[@0,@20,@40,@60,@70];
    dataCurveView.dataArrary = dataArray;
    dataCurveView.curveColor = [UIColor blueColor];
    dataCurveView.xMaxData = 70;
    dataCurveView.yMaxData = 200;
    [self.view addSubview:dataCurveView];
}

-(void)testDataCurveView2
{
    _dArray = [NSMutableArray array];
    //随机参数数据,模拟情况
    for( float i = 0; i<= 80 ;i+=1)
    {
        CGFloat value = arc4random() % 45 + 20;
        CGPoint point = CGPointMake(i, value);
        [self.dArray addObject:[NSValue valueWithCGPoint:point]];
    }
    
    JDataCurveView *dataCurveView = [JDataCurveView dataCurveView];
    dataCurveView.frame = CGRectMake(10, 340, self.view.frame.size.width - 20, 250);
    dataCurveView.isDynamicRefreshData = YES;
    dataCurveView.yCoordinateName = @"脉搏";
    dataCurveView.yCoordinateUnitName = @"次";
    dataCurveView.xCoordinateUnitName = @"无";
    dataCurveView.yDisplayData = @[@0,@20,@40,@60,@80];
    dataCurveView.xDisplayData = @[@0,@20,@40,@60,@80];
    dataCurveView.isNeedDisplayXData = NO;
    dataCurveView.dataArrary = self.dArray;
    dataCurveView.curveColor = [UIColor brownColor];
    dataCurveView.xMaxData = 80;
    dataCurveView.yMaxData = 80;
    _dataCurveView = dataCurveView;
    [self.view addSubview:dataCurveView];
    
    _timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];

-(void)timerAction
{
    NSMutableArray *array = [NSMutableArray array];
    for(int i = 1;i<self.dArray.count;i++)
    {
        NSValue *value = self.dArray[i];
        CGPoint point = [value CGPointValue];
        [array addObject:[NSValue valueWithCGPoint:CGPointMake(point.x - 1, point.y)]];

    }
    [self.dArray removeAllObjects];
    
    CGFloat value = arc4random() % 45 + 20;
    CGPoint point = CGPointMake(80, value);
    [array addObject:[NSValue valueWithCGPoint:point]];
    self.dArray = array;
    [_dataCurveView resetDataArray:self.dArray];
}
    
}

看看运行结果,因为会实时绘制,所以我做成了gif图片,便于展示,如下:

curveVideo.mov_1455780994.gif

代码上传github:https://github.com/jiangtaidi/DataCurveDemo.git 感兴趣的可以下载运行一下,希望对你有用!

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

推荐阅读更多精彩内容

  • 在上篇 在坐标系中绘制数据曲线 文章中,有人问如何在曲线下面加入渐变颜色,我尝试了一下,采用CAShapeLaye...
    jiangamh阅读 384评论 1 1
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,439评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,019评论 4 62
  • |1111|1111|11111| |11111111|2222||
    lean阅读 145评论 0 1
  • 作一幅小画 拟一首小诗 睡前总要画一首 如肉前总要喝一口 愿沉睡的你不被梦惊忧 可否到旧地一游 第一次写这种文字,...
    喵大人插画阅读 138评论 0 2