OC动态Y轴值波形图

参考了一篇心电图的demo,做了一个Y值需要动态变化的波形图。直接上代码


image.png

.h

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN
@class VariableYChartView;
@protocol VariableYChartViewDelegate <NSObject>

- (void)variableYChartViewClearDate:(VariableYChartView *)variableYChartView;

@end

@interface VariableYChartView : UIView
{
    //   绘图上下文
    CGContextRef contex;
}
//********** ↓↓↓↓  必须设置  ↓↓↓↓ ***********/
@property (nonatomic, readwrite , assign) NSInteger X_unitLength_copies;//每一刻度 分 多少份
@property (nonatomic, readwrite , assign) CGFloat fixedMinY;//如果设置了 固定最小值,y轴最小负值将不会改变。
//********** ↑↑↑↑  必须设置  ↑↑↑↑ ***********/
@property (nonatomic, readwrite , copy) NSString *nameStr;
@property (nonatomic, readwrite , assign) CGFloat X_unitLength;//x 刻度
@property (nonatomic, readwrite , assign) CGFloat Y_unitLength;//y 刻度
@property (nonatomic, readwrite , assign) CGFloat YAxisMaxValue;//Y轴 最大值
@property (nonatomic, readwrite , assign) CGFloat maxX;//最大 x 值||设置maxX要在X_unitLength_copies前
@property (nonatomic, readwrite , strong) UIColor *lineColor;//线 颜色
@property (nonatomic, readwrite , assign) CGFloat respieValue;//要绘制的呼吸波 新值
@property (nonatomic, readwrite , assign) CGFloat refreshWidth;//重绘 的宽度
@property (nonatomic, readwrite , weak) id<VariableYChartViewDelegate> delegate;
/// 画图
- (void)fireDrawing;
///清空
- (void)clearData;

.m

#import "VariableYChartView.h"

@interface VariableYChartView (){
    UILabel *_nameLabel;
    CGFloat _XlabelHeight;//x 刻度 label 高
    CGFloat _scaleMarkLength;//刻度线 长度
    CGFloat _margin;//间隔
    CGFloat _zeroY;//0 点 位置
    CGFloat _lineWidth;
    CGFloat _yScale;
    CGFloat _preX;//前一点 x 值
    CGFloat _perXLength;//每一个X的 实际长
    NSInteger _totalCopies;//X轴 总共 划分的 份数
    CGFloat _curMaxY;//记录的当前在最大Y值
    BOOL _isNeedRefreshAxis;//是否需要刷新 坐标轴
}

@end

@implementation VariableYChartView
- (instancetype)init
{
    self = [super init];
    if (self) {
        self.backgroundColor = [UIColor blackColor];
        self.clearsContextBeforeDrawing = YES;
        _fixedMinY = MAXFLOAT;
        _margin = 20.0;
        _YAxisMaxValue = 20.0;
        
        _X_unitLength = 2.0;
        _maxX = 14.0;
        _X_unitLength_copies = 34;
        _preX = _margin;
        
        _Y_unitLength = 20.0;
        
        _refreshWidth = 15.0;
        _scaleMarkLength = 5.0;//刻度线 画多长
        _margin = 20.0;
        
        _lineWidth = 1.0;
        _lineColor = [UIColor yellowColor];
        _isNeedRefreshAxis = NO;
        
        _nameLabel = [UILabel new];
        _nameLabel.font = SysFont(12.0);
        [self addSubview:_nameLabel];
        [_nameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.left.equalTo(self);
        }];
    }
    return self;
}
#pragma mark - 私有 方法
//画图
- (void)drawRect:(CGRect)rect{
    
    contex = UIGraphicsGetCurrentContext();
    [self drawAxis:contex];//画 坐标轴
    [self drawCurve:contex];
    
}
#pragma mark - 画 坐标轴
//抽代码-画 坐标轴
- (void)drawAxis:(CGContextRef)ctx{
    CGContextSetLineWidth(ctx, 1.0f);
    CGContextSetStrokeColorWithColor(ctx, [UIColor grayColor].CGColor);
    CGFloat Y_perLength;//一刻度 在按比例缩放到视图中的 实际长度
    //**********   画Y轴   ***********/
    CGContextMoveToPoint(ctx, _margin, _margin);
    CGContextAddLineToPoint(ctx, _margin, self.bounds.size.height - _margin);
    CGContextStrokePath(ctx);
    
    _YAxisMaxValue = [self calculateYAxisMaxValueWithY:_YAxisMaxValue];//y轴 最大 y 值
    //Y 轴刻度
    if (_fixedMinY != MAXFLOAT) {//如果 有最小固定值
        NSInteger count = -ceilf(_fixedMinY / _Y_unitLength);//求 Y负数有多少 格
        Y_perLength = _Y_unitLength / (_YAxisMaxValue - _fixedMinY) * (self.bounds.size.height - _margin - _margin);
        if (_fixedMinY != 0) {//= 0 就不用画 负数
            //画Y轴负值 部分
            for (NSInteger i=0; i<count; i++)
            {
                CGFloat Y = self.bounds.size.height - _margin - i*Y_perLength;
                //刻度
                [self context:ctx drawScaleMarkWithY:Y];
                //文字
                NSString *str = [NSString stringWithFormat:@"%.0f",-_Y_unitLength*(count - i)];
                [self drawTextWithStr:str atPoint:CGPointMake(0, Y)];
                CGContextStrokePath(ctx);
            }
        }
        
        //画 y轴正值
        NSInteger count2 = ceilf(_YAxisMaxValue/_Y_unitLength) + 1;
        for (NSInteger i=0; i<count2; i++)
        {
            CGFloat Y = i * Y_perLength + _margin;
            if (i == count2 - 1) { _zeroY = Y; }//记录 0 点
            //刻度
            [self context:ctx drawScaleMarkWithY:Y];
            CGContextStrokePath(ctx);
            //文字
            NSString *str = [NSString stringWithFormat:@"%.0f",(_YAxisMaxValue - _Y_unitLength*i)];
            [self drawTextWithStr:str atPoint:CGPointMake(0, Y)];
        }
        
    }else{
        //FIXME:1.没有设置_fixedMinY的情况,2._fixedMinY为正数的情况
    }
    //**********   画X轴   ***********/
    CGContextSetStrokeColorWithColor(ctx, [UIColor grayColor].CGColor);
    CGContextMoveToPoint(ctx, _margin, _zeroY);
    CGContextAddLineToPoint(ctx, self.bounds.size.width - _margin, _zeroY);
    CGContextStrokePath(ctx);
    //**********   底部 时间轴   ***********/
    CGFloat bottomY = self.bounds.size.height - _margin;
    CGContextMoveToPoint(ctx, _margin, bottomY);
    CGContextAddLineToPoint(ctx, self.bounds.size.width - _margin, bottomY);
    CGContextStrokePath(ctx);
    CGFloat X_perLenth = _X_unitLength / _maxX * (self.bounds.size.width - _margin - _margin);
    //画X轴 刻度线
    for (NSInteger i=0; i<_maxX/_X_unitLength; i++)
    {
        CGFloat X = (i + 1) * X_perLenth + _margin;
        //画 刻度
        CGContextMoveToPoint(ctx, X, bottomY);
        CGContextAddLineToPoint(ctx, X, bottomY+_scaleMarkLength);
        CGContextStrokePath(ctx);
        //画 文字
        NSString *str = [NSString stringWithFormat:@"%.0fs",_X_unitLength * (i + 1)];
        [self drawTextWithStr:str atPoint:CGPointMake(X, bottomY+_scaleMarkLength)];
    }
//    CGContextSetLineWidth(ctx, 2.0);
    
    CGContextSetLineWidth(ctx, 4.0f);
    CGContextStrokePath(ctx);
}
//抽代码- 画刻度 和 文字
- (void)context:(CGContextRef)ctx drawScaleMarkWithY:(CGFloat)Y{
    CGContextMoveToPoint(ctx, _margin-_scaleMarkLength, Y);
    CGContextAddLineToPoint(ctx, _margin, Y);
}
//抽代码- 画文字
- (void)drawTextWithStr:(NSString *)str atPoint:(CGPoint)point{
    // 文本属性
   NSMutableDictionary *textAttributes = [[NSMutableDictionary alloc] init];
   // NSFontAttributeName 字体名称和大小
   [textAttributes setValue:[UIFont systemFontOfSize:10.0] forKey:NSFontAttributeName];
   // NSForegroundColorAttributeNam 颜色
   [textAttributes setValue:[UIColor grayColor] forKey:NSForegroundColorAttributeName];
   [str drawAtPoint:point withAttributes:textAttributes];
}
//抽代码- 计算 Y轴最大值
- (CGFloat)calculateYAxisMaxValueWithY:(CGFloat)Y{
    if (Y < 0) { return _Y_unitLength; }//如果最大值 小于0 了 取一刻度
    CGFloat value = ceilf(Y / _Y_unitLength) * _Y_unitLength;
    return value;
}

#pragma mark - 画图
// 绘制呼吸波图型
- (void)drawCurve:(CGContextRef)ctx{
    CGContextSetFillColorWithColor(ctx, _lineColor.CGColor);
    CGContextSetStrokeColorWithColor(ctx, _lineColor.CGColor);
    CGFloat H = _respieValue*_yScale;
    //公式:(要画的点取反 - Y轴最小值 + Y轴正最大值比负最大值多的部分)* 比例 + 间距   | 间距是真实view的值,其他是未换算的值 *比例 换算为真实view值。
    CGFloat Y = (-_respieValue - _fixedMinY + (_YAxisMaxValue + _fixedMinY)) * _yScale + _margin;
//    NSLog(@"CGContextFillRect:%@",NSStringFromCGRect(rect));
    //填充 和 边框 都要,不然会出现 颜色不能完全填充 渐变的间隙--线宽够宽,可以弥补 间隙
    //
//    CGRect rect = CGRectMake(_preX, Y, _perXLength, H);
//    CGContextStrokeRect(ctx, rect);//画方框
//    CGContextFillRect(ctx, rect);//填充
    CGContextMoveToPoint(ctx, _preX + _perXLength/2, Y);
    CGContextAddLineToPoint(ctx, _preX + _perXLength/2, Y + H);
    CGContextStrokePath(ctx);
}
#pragma mark - 公开 方法
- (void)fireDrawing
{
    if (!_yScale) {
        _yScale = (self.bounds.size.height - 2*_margin)/(_YAxisMaxValue - _fixedMinY);
        _perXLength = (self.bounds.size.width - 2*_margin) / _totalCopies;//因为_yScale、_perXLength同时存在,可以一个判断
    }
    //改变 x 向前移一个单位
    if (_preX < self.bounds.size.width - _margin) {
        _preX = _preX + _perXLength;
    }else{
        if (_isNeedRefreshAxis) {//如果 上次 记录了 需要 刷新(当传入最大值 缩小了一刻度,Y轴就需要 重画 如:刻度从(0、20、40)-> (0、20))
            _isNeedRefreshAxis = NO;
            CGFloat curTempMaxY = [self calculateYAxisMaxValueWithY:_curMaxY];
            if (curTempMaxY != _YAxisMaxValue) {
                _YAxisMaxValue = curTempMaxY;
                [self clearData];
                [self setNeedsDisplay];
            }
        }
        CGFloat temp = [self calculateYAxisMaxValueWithY:_respieValue];//计算传入的点 对应 最大 Y轴值 对比当前最大Y轴值 是否相同
        if (temp < _YAxisMaxValue) {
            //TODO:记录本次坐标系中所有点,在setRespieValue中记录
            _isNeedRefreshAxis = YES;
        }
        _preX = _margin+2;
        _curMaxY = _respieValue;
    }
    CGRect reck = CGRectMake( _preX, _margin,    _refreshWidth, self.bounds.size.height - 2*_margin-2);
    [self setNeedsDisplayInRect:reck];

}
- (void)clearData{
    if ([self.delegate respondsToSelector:@selector(variableYChartViewClearDate:)]) {
        [self.delegate variableYChartViewClearDate:self];
    }
    _yScale = 0.0;
    _perXLength = 0.0;
    _preX = _margin;
    _isNeedRefreshAxis = NO;
    [self setNeedsDisplay];
}
#pragma mark - setter
- (void)setRespieValue:(CGFloat)respieValue{
    _respieValue = respieValue;
    _curMaxY = MAX(_curMaxY, _respieValue);
    if (respieValue <= _YAxisMaxValue) {return;} //传入的 值 小于等于 Y轴最大正值 返回 否则 更换坐标系
    _YAxisMaxValue = [self calculateYAxisMaxValueWithY:respieValue];
    [self clearData];
    
}
- (void)setX_unitLength_copies:(NSInteger)X_unitLength_copies{
    _X_unitLength_copies = X_unitLength_copies;
    _totalCopies = _maxX * _X_unitLength_copies;
}
- (void)setLineColor:(UIColor *)lineColor{
    _lineColor = lineColor;
    _nameLabel.textColor = lineColor;
}
- (void)setNameStr:(NSString *)nameStr{
    _nameStr = nameStr;
    _nameLabel.text = nameStr;
}
@end

使用方法

@property (nonatomic, readwrite , strong) VariableYChartView *respireWave;
//假数据
_dataArr = @[@22,@42,@63,@83,@102,@83,@63,@42,@22,@10];
//创建
CGFloat w = [UIScreen mainScreen].bounds.size.width;
CGFloat h = [UIScreen mainScreen].bounds.size.height;
_respireWave = [[VariableYChartView alloc] init];
_respireWave.frame = CGRectMake(50, 50, w-100, (w-100) * 0.6);
_respireWave.nameStr = @"Paw mbar";
_respireWave.delegate = self;
_respireWave.maxX = 5;
[_respireWave setX_unitLength_copies:34];
[_respireWave setFixedMinY:-20.0];
[_respireWave setLineColor:[UIColor yellowColor]];
[_respireWave setRefreshWidth:5.0];
[self.view addSubview:_respireWave];

//开始绘制
[NSTimer scheduledTimerWithTimeInterval:0.01 repeats:YES block:^(NSTimer * _Nonnull timer) {
    self.cnt++;
    NSInteger index = self.cnt / (34*5);
    //关键代码,下面两行
    self.respireWave.respieValue = [self.dataArr[index] floatValue];
    [self.respireWave fireDrawing];
    if (self.cnt == (self.dataArr.count-1)*34*5) {
        self.cnt = 0;
    }
    self.cnt1++;
}];

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容