参考了一篇心电图的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++;
}];