前言:
最近公司在藍(lán)牙設(shè)備,需要按照通過設(shè)備讀取的數(shù)據(jù)來實(shí)時(shí)畫出折線圖,參考了很多資料,然后自己封裝了一套畫折線圖的方法(支持畫封閉圖形,四邊形,三角形),如果有需要的小伙伴可以拿去,效果圖如下:

Untitled001.gif

Untitled.gif

Untitled002.gif
首先
我自定義了一個(gè)專門真是折線圖的 View ,我新建的時(shí)候用了 xib, 當(dāng)然不用也是可以的只需要自己手動(dòng)調(diào)一下就好了

Paste_Image.png
具體實(shí)現(xiàn)方法如下:
- SportLineView.h
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSUInteger, ChartType) {
/** 四邊形*/
QuadrilateralType = 1,
/** 三角形 */
TriangleType = 2
};
@interface SportLineView : UIView
// x軸值
@property (nonatomic, copy) NSArray *xValues;
// y軸值
@property (nonatomic, copy) NSArray *yValues;
// 繪圖數(shù)組
@property (strong, nonatomic) NSMutableArray *pointArray;
// 是否顯示方格
@property (nonatomic, assign) bool isShowLine;
// 初始化折線圖所在視圖
+ (instancetype)lineChartViewWithFrame:(CGRect)frame;
// 畫圖表
- (void)drawChartWithLineChart;
// 封閉圖形類型
@property (nonatomic, assign) ChartType type;
// 即時(shí)更新折線圖
- (void)exchangeLineAnyTime;
// 一類肌疲勞度
- (CGFloat)getFirstMuscleLevel;
// 二類肌疲勞度
- (CGFloat)getSecondMuscleLevel;
@end
- SportLineView.m
#import "SportLineView.h"
static CGRect myFrame;
static int count; // 點(diǎn)個(gè)數(shù),x軸格子數(shù)
static int yCount; // y軸格子數(shù)
static CGFloat everyX; // x軸每個(gè)格子寬度
static CGFloat everyY; // y軸每個(gè)格子高度
static CGFloat maxY; // 最大的y值
static CGFloat allH; // 整個(gè)圖表高度
static CGFloat allW; // 整個(gè)圖表寬度
#define kMargin 30
@interface SportLineView()
@property (weak, nonatomic) IBOutlet UIView *bgView;
@property (strong, nonatomic) NSMutableArray *xLabels;
@end
@implementation SportLineView
+ (instancetype)lineChartViewWithFrame:(CGRect)frame{
SportLineView *lineChartView = [[NSBundle mainBundle] loadNibNamed:@"SportLineView" owner:self options:nil].lastObject;
lineChartView.frame = frame;
myFrame = frame;
return lineChartView;
}
#pragma mark - 計(jì)算
- (void)doWithCalculate{
if (!self.xValues || !self.xValues.count || !self.yValues || !self.yValues.count) {
return;
}
// 移除多余的值,計(jì)算點(diǎn)個(gè)數(shù)
if (self.xValues.count > self.yValues.count) {
NSMutableArray * xArr = [self.xValues mutableCopy];
for (int i = 0; i < self.xValues.count - self.yValues.count; i++){
[xArr removeLastObject];
}
self.xValues = [xArr mutableCopy];
}else if (self.xValues.count < self.yValues.count){
NSMutableArray * yArr = [self.yValues mutableCopy];
for (int i = 0; i < self.yValues.count - self.xValues.count; i++){
[yArr removeLastObject];
}
self.yValues = [yArr mutableCopy];
}
count = (int)self.xValues.count;
everyX = (CGFloat)(CGRectGetWidth(myFrame) - kMargin * 2) / count;
// y軸最多分5部分
yCount = count <= 10 ? count : 10;
everyY = (CGRectGetHeight(myFrame) - kMargin * 2) / yCount;
maxY = CGFLOAT_MIN;
for (int i = 0; i < count; i ++) {
if ([self.yValues[i] floatValue] > maxY) {
maxY = [self.yValues[i] floatValue];
}
}
allH = CGRectGetHeight(myFrame) - kMargin * 2;
allW = CGRectGetWidth(myFrame) - kMargin * 2;
}
#pragma mark - 畫X、Y軸
- (void)drawXYLine{
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(kMargin, kMargin / 2.0 - 5)];
[path addLineToPoint:CGPointMake(kMargin, CGRectGetHeight(myFrame) - kMargin)];
[path addLineToPoint:CGPointMake(CGRectGetWidth(myFrame) - kMargin / 2.0 + 5, CGRectGetHeight(myFrame) - kMargin)];
// 加箭頭
[path moveToPoint:CGPointMake(kMargin - 5, kMargin/ 2.0 + 4)];
[path addLineToPoint:CGPointMake(kMargin, kMargin / 2.0 - 4)];
[path addLineToPoint:CGPointMake(kMargin + 5, kMargin/ 2.0 + 4)];
[path moveToPoint:CGPointMake(CGRectGetWidth(myFrame) - kMargin / 2.0 - 4, CGRectGetHeight(myFrame) - kMargin - 5)];
[path addLineToPoint:CGPointMake(CGRectGetWidth(myFrame) - kMargin / 2.0 + 5, CGRectGetHeight(myFrame) - kMargin)];
[path addLineToPoint:CGPointMake(CGRectGetWidth(myFrame) - kMargin / 2.0 - 4, CGRectGetHeight(myFrame) - kMargin + 5)];
CAShapeLayer *layer = [[CAShapeLayer alloc] init];
layer.path = path.CGPath;
layer.strokeColor = [UIColor brownColor].CGColor;
layer.fillColor = [UIColor clearColor].CGColor;
layer.lineWidth = 2.0;
[self.layer addSublayer:layer];
}
#pragma mark - 添加label
- (void)drawLabels{
//Y軸
for(int i = 0; i <= yCount; i ++){
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(0, kMargin + everyY * i - everyY / 2, kMargin - 1, everyY)];
lbl.textColor = [UIColor blackColor];
lbl.font = [UIFont systemFontOfSize:10];
lbl.textAlignment = NSTextAlignmentRight;
lbl.text = [NSString stringWithFormat:@"%d%%", (int)(maxY / yCount * (yCount - i)) ];
[self addSubview:lbl];
}
// X軸
for(int i = 1; i <= count; i ++){
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(kMargin + everyX * i - everyX / 2, CGRectGetHeight(myFrame) - kMargin, everyX, kMargin)];
lbl.textColor = [UIColor blackColor];
lbl.font = [UIFont systemFontOfSize:12];
lbl.textAlignment = NSTextAlignmentCenter;
[self.xLabels addObject:lbl];
// 如果起點(diǎn)不是0,計(jì)算橫軸的坐標(biāo)值
NSValue *pointObj = [self.pointArray lastObject];
CGPoint pointRestored = [pointObj CGPointValue];
CGFloat maxX = pointRestored.x;
int maxValueX = (int)maxX;
CGFloat xfloat = maxX - maxValueX;
if (xfloat > 0) {
maxValueX += 1;
}
// NSValue *firstPointObj = [self.pointArray firstObject];
// CGPoint firstPointRestored = [firstPointObj CGPointValue];
if (maxValueX <= count) {
lbl.text = [NSString stringWithFormat:@"%@", self.xValues[i - 1]];
}else{
lbl.text = [NSString stringWithFormat:@"%zd", maxValueX - count + i];
}
[self addSubview:lbl];
}
}
#pragma mark - 畫網(wǎng)格
- (void)drawLines{
UIBezierPath *path = [UIBezierPath bezierPath];
// 橫線
for (int i = 0; i < yCount; i ++) {
[path moveToPoint:CGPointMake(kMargin , kMargin + everyY * i)];
[path addLineToPoint:CGPointMake(kMargin + allW , kMargin + everyY * i)];
}
// 豎線
for (int i = 1; i <= count; i ++) {
[path moveToPoint:CGPointMake(kMargin + everyX * i, kMargin)];
[path addLineToPoint:CGPointMake( kMargin + everyX * i, kMargin + allH)];
}
CAShapeLayer *layer = [[CAShapeLayer alloc] init];
layer.path = path.CGPath;
layer.strokeColor = [UIColor lightGrayColor].CGColor;
layer.fillColor = [UIColor clearColor].CGColor;
layer.lineWidth = 0.5;
[self.layer addSublayer:layer];
}
#pragma mark - 畫折線\曲線
- (void)drawFoldLineWithLineChart{
UIBezierPath *path = [UIBezierPath bezierPath];
NSValue *pointObj = [self.pointArray firstObject];
CGPoint pointRestored = [pointObj CGPointValue];
CGFloat xpoint = pointRestored.x;
CGFloat ypoint = pointRestored.y;
int maxValueX = count;
CGFloat Xwidth = (CGFloat)(CGRectGetWidth(myFrame) - kMargin * 2);
if (xpoint <= 0) {
[path moveToPoint:CGPointMake(kMargin, kMargin + allH)];
}else{
// 如果起點(diǎn)不是0,計(jì)算橫軸的坐標(biāo)值
NSValue *MaxpointObj = [self.pointArray lastObject];
CGPoint MaxpointRestored = [MaxpointObj CGPointValue];
CGFloat maxX = MaxpointRestored.x;
maxValueX = (int)maxX;
CGFloat xfloat = maxX - maxValueX;
if (xfloat > 0) {
maxValueX += 1;
}
if (maxValueX <= count) {
[path moveToPoint:CGPointMake(kMargin + xpoint * Xwidth / count, kMargin + (1 - ypoint / maxY) * allH)];
}else{
for (int i = 1; i < self.pointArray.count; i ++) {
NSValue *pointObj = self.pointArray[i];
CGPoint pointRestored = [pointObj CGPointValue];
if (pointRestored.x >= (maxValueX - count)) {
[path moveToPoint:CGPointMake(kMargin + (pointRestored.x - (maxValueX - count))* Xwidth / count, kMargin + (1 - pointRestored.y / maxY) * allH)];
i = (int)(self.pointArray.count + 1);
}
}
}
}
for (int i = 1; i < self.pointArray.count; i ++) {
NSValue *pointObj = self.pointArray[i];
CGPoint pointRestored = [pointObj CGPointValue];
if (maxValueX <= count) {
[path addLineToPoint:CGPointMake(kMargin + pointRestored.x* Xwidth / count, kMargin + (1 - pointRestored.y / maxY) * allH)];
}else{
if (pointRestored.x >= (maxValueX - count)) {
[path addLineToPoint:CGPointMake(kMargin + (pointRestored.x - (maxValueX - count))* Xwidth / count, kMargin + (1 - pointRestored.y / maxY) * allH)];
}
}
}
if (self.pointArray.count == 0) {
[path addLineToPoint:CGPointMake(kMargin, kMargin + allH)];
}
CAShapeLayer *layer = [[CAShapeLayer alloc] init];
layer.path = path.CGPath;
layer.strokeColor = [UIColor redColor].CGColor;
layer.fillColor = [UIColor clearColor].CGColor;
[self.bgView.layer addSublayer:layer];
}
#pragma mark - 整合 畫圖表
- (void)drawChartWithLineChart{
// 計(jì)算賦值
[self doWithCalculate];
// 畫網(wǎng)格線
if (self.isShowLine) {
[self drawLines];
}
// 畫X、Y軸
[self drawXYLine];
// 添加文字
[self drawLabels];
// 畫封閉圖形
if (self.type == QuadrilateralType) {
NSMutableArray *points = [NSMutableArray array];
CGPoint point1 = CGPointMake(1.5, 0);
NSValue *pointObj1 = [NSValue valueWithCGPoint:point1];
[points addObject:pointObj1];
CGPoint point2 = CGPointMake(2, 45);
NSValue *pointObj2 = [NSValue valueWithCGPoint:point2];
[points addObject:pointObj2];
CGPoint point3 = CGPointMake(8, 45);
NSValue *pointObj3 = [NSValue valueWithCGPoint:point3];
[points addObject:pointObj3];
CGPoint point4 = CGPointMake(8.5, 0);
NSValue *pointObj4 = [NSValue valueWithCGPoint:point4];
[points addObject:pointObj4];
[self drawClosedQuadrilateralChartWithArray:points];
}else if(self.type == TriangleType){
for (int i = 0; i < 5; i ++) {
NSMutableArray *points = [NSMutableArray array];
CGPoint point1 = CGPointMake(2 * (i+ 1), 0);
NSValue *pointObj1 = [NSValue valueWithCGPoint:point1];
[points addObject:pointObj1];
CGPoint point2 = CGPointMake(2 * (i+ 1) + 0.5, 100);
NSValue *pointObj2 = [NSValue valueWithCGPoint:point2];
[points addObject:pointObj2];
CGPoint point3 = CGPointMake(2 * (i+ 1) + 1, 0);
NSValue *pointObj3 = [NSValue valueWithCGPoint:point3];
[points addObject:pointObj3];
[self drawClosedQuadrilateralChartWithArray:points];
}
}
}
// 更新折線圖, X軸坐標(biāo)
- (void)exchangeLineAnyTime{
// 計(jì)算賦值
[self doWithCalculate];
NSArray *layers = [self.bgView.layer.sublayers mutableCopy];
for (CAShapeLayer *layer in layers) {
[layer removeFromSuperlayer];
}
// 畫折線
[self drawFoldLineWithLineChart];
[self exchangeXlabels];
}
- (void)exchangeXlabels{
NSValue *pointObj = [self.pointArray lastObject];
CGPoint pointRestored = [pointObj CGPointValue];
CGFloat maxX = pointRestored.x;
int maxValueX = (int)maxX;
CGFloat xfloat = maxX - maxValueX;
if (xfloat > 0) {
maxValueX += 1;
}
if (maxValueX > count) {
for (int i = 0; i < self.xLabels.count; i ++) {
UILabel *label = self.xLabels[i];
label.text = [NSString stringWithFormat:@"%zd", maxValueX - count + i + 1];
}
}
}
// 封閉圖形
- (void)drawClosedQuadrilateralChartWithArray:(NSArray *)points{
UIBezierPath *path = [UIBezierPath bezierPath];
CGFloat Xwidth = (CGFloat)(CGRectGetWidth(myFrame) - kMargin * 2);
for (int i = 0; i < points.count; i ++) {
NSValue *pointObj = points[i];
CGPoint pointRestored = [pointObj CGPointValue];
if (i == 0) {
[path moveToPoint:CGPointMake(kMargin + pointRestored.x * Xwidth / count, kMargin + (1 - pointRestored.y / maxY) * allH)];
}else{
[path addLineToPoint:CGPointMake(kMargin + pointRestored.x * Xwidth / count, kMargin + (1 - pointRestored.y / maxY) * allH)];
}
}
[path closePath];
CAShapeLayer *layer = [[CAShapeLayer alloc] init];
layer.path = path.CGPath;
layer.strokeColor = [UIColor lightGrayColor].CGColor;
layer.fillColor = SSColorA(255, 255, 155, 100.0).CGColor;
[self.layer addSublayer:layer];
}
#pragma mark -- 一類肌疲勞度
- (CGFloat)getFirstMuscleLevel{
CGPoint startPoint = [self exchangeXYValuePoint:CGPointMake(2, 40)];
CGPoint endPoint = [self exchangeXYValuePoint:CGPointMake(7, 40)];
CGFloat totalArea = (endPoint.x - startPoint.x) * endPoint.y;
CGFloat trapezoidal = 0;
for (int i = 0; i < self.pointArray.count; i ++) {
NSValue *pointObj = self.pointArray[i];
CGPoint pointRestored = [pointObj CGPointValue];
if (pointRestored.x >= 2 && pointRestored.x <= 7) {
NSValue *pointObj2 = self.pointArray[i + 1];
CGPoint pointRestored2 = [pointObj2 CGPointValue];
CGPoint point1 = pointRestored;
CGPoint point2 = pointRestored2;
CGFloat area = [self getTrapezoidalAreaWithPoint1:point1 poit2:point2];
trapezoidal += area;
}
}
// 計(jì)算肌力疲勞度
CGFloat level = trapezoidal / totalArea;
return level;
}
#pragma mark -- 二類肌疲勞度
- (CGFloat)getSecondMuscleLevel{
CGPoint startPoint = [self exchangeXYValuePoint:CGPointMake(2, 0)];
CGPoint endPoint = [self exchangeXYValuePoint:CGPointMake(3, 0)];
CGPoint topPoint = [self exchangeXYValuePoint:CGPointMake(2.5, 100)];
CGFloat triangle = (endPoint.x - startPoint.x) * topPoint.y * 0.5;
CGFloat totalArea = 5 * triangle;
CGFloat trapezoidal = 0;
for (int i = 0; i < self.pointArray.count; i ++) {
NSValue *pointObj = self.pointArray[i];
CGPoint pointRestored = [pointObj CGPointValue];
if (pointRestored.x >= 2 && pointRestored.x <= 3) {
NSValue *pointObj2 = self.pointArray[i + 1];
CGPoint pointRestored2 = [pointObj2 CGPointValue];
CGPoint point1 = pointRestored;
CGPoint point2 = pointRestored2;
CGFloat area = [self getTrapezoidalAreaWithPoint1:point1 poit2:point2];
trapezoidal += area;
}else if (pointRestored.x >= 4 && pointRestored.x <= 5){
NSValue *pointObj2 = self.pointArray[i + 1];
CGPoint pointRestored2 = [pointObj2 CGPointValue];
CGPoint point1 = pointRestored;
CGPoint point2 = pointRestored2;
CGFloat area = [self getTrapezoidalAreaWithPoint1:point1 poit2:point2];
trapezoidal += area;
}else if (pointRestored.x >= 6 && pointRestored.x <= 7){
NSValue *pointObj2 = self.pointArray[i + 1];
CGPoint pointRestored2 = [pointObj2 CGPointValue];
CGPoint point1 = pointRestored;
CGPoint point2 = pointRestored2;
CGFloat area = [self getTrapezoidalAreaWithPoint1:point1 poit2:point2];
trapezoidal += area;
}else if (pointRestored.x >= 8 && pointRestored.x <= 9){
NSValue *pointObj2 = self.pointArray[i + 1];
CGPoint pointRestored2 = [pointObj2 CGPointValue];
CGPoint point1 = pointRestored;
CGPoint point2 = pointRestored2;
CGFloat area = [self getTrapezoidalAreaWithPoint1:point1 poit2:point2];
trapezoidal += area;
}else if (pointRestored.x >= 10 && pointRestored.x <= 11){
NSValue *pointObj2 = self.pointArray[i + 1];
CGPoint pointRestored2 = [pointObj2 CGPointValue];
CGPoint point1 = pointRestored;
CGPoint point2 = pointRestored2;
CGFloat area = [self getTrapezoidalAreaWithPoint1:point1 poit2:point2];
trapezoidal += area;
}
}
// 計(jì)算肌力疲勞度
CGFloat level = trapezoidal / totalArea;
return level;
}
// 計(jì)算兩坐標(biāo)點(diǎn)之間的梯形面積
- (CGFloat)getTrapezoidalAreaWithPoint1:(CGPoint)point1 poit2:(CGPoint)point2{
CGPoint expoint1 = [self exchangeXYValuePoint:point1];
CGPoint expoint2 = [self exchangeXYValuePoint:point2];
CGFloat expoint1X = expoint1.x;
CGFloat expoint1y = expoint1.y;
CGFloat expoint2X = expoint2.x;
CGFloat expoint2y = expoint2.y;
CGFloat area = (expoint1y + expoint2y) * (expoint2X - expoint1X) * 0.5;
return area;
}
// 根據(jù)一個(gè)點(diǎn)轉(zhuǎn)換坐標(biāo)值
- (CGPoint)exchangeXYValuePoint:(CGPoint)point{
CGFloat Xwidth = (CGFloat)(CGRectGetWidth(myFrame) - kMargin * 2);
CGPoint zeroPoint;
zeroPoint.x = kMargin + 0 * Xwidth / count;
zeroPoint.y = kMargin + (1 - 0 / maxY) * allH;
CGPoint linePoint;
linePoint.x = kMargin + point.x * Xwidth / count - zeroPoint.x;
linePoint.y = zeroPoint.y - ( kMargin + (1 - point.y / maxY) * allH );
return linePoint;
}
#pragma mark -- 懶加載
- (NSMutableArray *)pointArray{
if (!_pointArray) {
_pointArray = [NSMutableArray array];
}
return _pointArray;
}
- (NSMutableArray *)xLabels{
if (!_xLabels) {
_xLabels = [NSMutableArray array];
}
return _xLabels;
}
@end
具體使用方法:
- 初始化:
SportLineView *LCView = [SportLineView lineChartViewWithFrame:CGRectMake(10, 0, CGRectGetWidth([UIScreen mainScreen].bounds) - 20, self.topView.frame.size.height)];
LCView.xValues = @[@1, @2, @3, @4, @5, @6, @7,@8, @9, @10];
LCView.yValues = @[@10, @20, @30, @40, @50,@60, @70, @80,@90, @100];
// 設(shè)置封閉圖形的樣式
LCView.type = QuadrilateralType;
self.LCView = LCView;
LCView.isShowLine = YES;
[LCView drawChartWithLineChart];
[self.topView addSubview:LCView];
- 然后在接收數(shù)據(jù)的時(shí)候,調(diào)用實(shí)施更新的方法:
// 把點(diǎn)加入到數(shù)組中
CGPoint point = CGPointMake(x, y);
NSValue *pointObj = [NSValue valueWithCGPoint:point];
[weakSelf.LCView.pointArray addObject:pointObj];
for (int i = 0; i < weakSelf.LCView.pointArray.count; i ++) {
NSValue *pointObj = weakSelf.LCView.pointArray[i];
CGPoint pointRestored = [pointObj CGPointValue];
// 如果 X 軸數(shù)值大于,X 軸會(huì)自動(dòng)往后移動(dòng)
if (x > weakSelf.LCView.xValues.count) {
// 移除沒有展示出來的點(diǎn),不然數(shù)組里存放的太多了,內(nèi)容會(huì)爆棚
if (pointRestored.x < (x - weakSelf.LCView.xValues.count) ) {
[weakSelf.LCView.pointArray removeObject:pointObj];
}
}
}
// 調(diào)用實(shí)時(shí)更新折線圖的方法
[weakSelf.LCView exchangeLineAnyTime];