GitHub地址:https://github.com/runThor/HTChart
可支持左右拖动、双指放大缩小操作。
学习过PNChart的源码,这里是一个最基础、很简化的折线图。
效果:
实现:
折线类:
// HTLine.h
#import <UIKit/UIKit.h>
@interface HTLine : UIView
@property (nonatomic, strong) NSMutableArray *dataArr; // 此条折线的数据
@property (nonatomic, strong) UIColor *lineColor; // 此条折线的颜色
@end
// HTLine.m
#import "HTLine.h"
@implementation HTLine
- (instancetype)init {
if (self = [super init]) {
self.dataArr = [[NSMutableArray alloc] init];
}
return self;
}
@end
图表类:
// HTChartView.h
#import <UIKit/UIKit.h>
#import "HTLine.h"
@interface HTChartView : UIView
@property (nonatomic, assign) CGFloat maxValue; // y轴的上限值
@property (nonatomic, assign) CGFloat minValue; // y轴的下限值
- (void)addLines:(NSArray *)lines; // 往图表中添加折线
@end
// HTChartView.m
#import "HTChartView.h"
#define VIEW_WIDTH self.frame.size.width
#define VIEW_HEIGHT self.frame.size.height
#define MAX_POINT_INTERVAL 60 // 左右相邻点最大间隔,控制放大极限
#define MIN_POINT_INTERVAL 5 // 左右相邻点最小间隔,控制缩小极限
@interface HTChartView ()
@property (nonatomic, assign) CGFloat topMargin; // 与屏幕上边距
@property (nonatomic, assign) CGFloat bottomMargin; // 与屏幕下边距
@property (nonatomic, assign) CGFloat leftMargin; // 与屏幕左边距
@property (nonatomic, assign) CGFloat rightMargin; // 与屏幕右边距
@property (nonatomic, assign) NSInteger horizontalValueLinesCount; // 从y轴刻度处延伸出x轴水平线的数量,即y轴上的刻度个数
@property (nonatomic, assign) CGFloat contentOffset; // 折线图的偏移量,正数,最小为0
@property (nonatomic, assign) CGFloat pointInterval; // 左右相邻数据点的间隔
@property (nonatomic, strong) NSMutableArray *linesArr; // 所有折线
@property (nonatomic, strong) NSMutableArray *dataOriginArr; // 所有折线的数据点位置
@end
@implementation HTChartView
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:1];
// 设置图表的初始状态
self.topMargin = 20.0;
self.bottomMargin = 20.0;
self.leftMargin = 30.0;
self.rightMargin = 10.0;
self.horizontalValueLinesCount = 5;
self.pointInterval = 40;
self.linesArr = [[NSMutableArray alloc] init];
}
return self;
}
- (void)drawRect:(CGRect)rect {
// 绘制图表框架
[self drawChartFrame];
// 绘制折线
[self drawLines];
}
// 绘制图表框架
- (void)drawChartFrame {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
// 画y轴
CGContextMoveToPoint(context, self.leftMargin, self.topMargin);
CGContextAddLineToPoint(context, self.leftMargin, VIEW_HEIGHT - self.bottomMargin);
// 画x轴
CGContextAddLineToPoint(context, VIEW_WIDTH - self.rightMargin, VIEW_HEIGHT - self.bottomMargin);
// 画右边框
CGContextAddLineToPoint(context, VIEW_WIDTH - self.rightMargin, self.topMargin);
// 绘制
CGContextStrokePath(context);
// 绘制x轴的水平线
CGContextSetRGBStrokeColor(context, 1, 1, 1, 0.3);
CGFloat eachHeight = (VIEW_HEIGHT - self.topMargin - self.bottomMargin)/(self.horizontalValueLinesCount + 1);
for (int i = 1 ; i <= self.horizontalValueLinesCount; i++) {
CGContextMoveToPoint(context, self.leftMargin, self.topMargin + eachHeight * i);
CGContextAddLineToPoint(context, VIEW_WIDTH - self.rightMargin, self.topMargin + eachHeight * i);
}
CGContextStrokePath(context);
}
// 绘制折线
- (void)drawLines {
CGContextRef context = UIGraphicsGetCurrentContext();
for (int lineIndex = 0; lineIndex < self.linesArr.count; lineIndex++) {
HTLine *line = self.linesArr[lineIndex];
// 计算数据点位置
NSMutableArray *originArr = [[NSMutableArray alloc] init];
for (int dataIndex = 0; dataIndex < line.dataArr.count; dataIndex++) {
CGFloat x = self.leftMargin + dataIndex * self.pointInterval - self.contentOffset;
CGFloat y = VIEW_HEIGHT - ([line.dataArr[dataIndex] floatValue]/(self.maxValue - self.minValue) * (VIEW_HEIGHT - self.topMargin - self.bottomMargin) + self.bottomMargin);
[originArr addObject:NSStringFromCGPoint(CGPointMake(x, y))];
}
[self.dataOriginArr addObject:originArr];
// 绘制数据点
CGContextSetFillColorWithColor(context, line.lineColor.CGColor);
for (NSString *origin in originArr) {
CGPoint dataOrigin = CGPointFromString(origin);
if (dataOrigin.x >= self.leftMargin) {
if (dataOrigin.x > VIEW_WIDTH - self.rightMargin) {
break;
} else {
CGContextFillEllipseInRect(context, CGRectMake(dataOrigin.x - 2.5, dataOrigin.y - 2.5, 5, 5));
}
}
}
// 各数据点连成折线
CGContextSetStrokeColorWithColor(context, line.lineColor.CGColor);
BOOL startDrawing = NO;
for (int dataIndex = 0; dataIndex < originArr.count; dataIndex++) {
CGPoint dataOrigin = CGPointFromString(originArr[dataIndex]);
if (startDrawing == NO) {
if (dataOrigin.x >= self.leftMargin) {
if (dataIndex == 0) {
CGContextMoveToPoint(context, dataOrigin.x, dataOrigin.y);
} else {
// 与y轴的交点
CGPoint lastDataOrigin = CGPointFromString(originArr[dataIndex - 1]);
CGFloat startPointY = dataOrigin.y - (dataOrigin.x - self.leftMargin)/self.pointInterval * (dataOrigin.y - lastDataOrigin.y);
CGContextMoveToPoint(context, self.leftMargin, startPointY);
CGContextAddLineToPoint(context, dataOrigin.x, dataOrigin.y);
}
startDrawing = YES;
}
} else {
if (dataOrigin.x >= VIEW_WIDTH - self.rightMargin) {
// 与右边框的交点
CGPoint lastDataOrigin = CGPointFromString(originArr[dataIndex - 1]);
CGFloat endPointY = dataOrigin.y - (dataOrigin.x - (VIEW_WIDTH - self.rightMargin))/self.pointInterval * (dataOrigin.y - lastDataOrigin.y);
CGContextAddLineToPoint(context, VIEW_WIDTH - self.rightMargin, endPointY);
break;
} else {
CGContextAddLineToPoint(context, dataOrigin.x, dataOrigin.y);
}
}
}
CGContextStrokePath(context);
}
}
// 往图表中添加折线
- (void)addLines:(NSArray *)lines {
[self.linesArr addObjectsFromArray:lines];
[self setNeedsDisplay];
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
CGFloat lineLength = self.pointInterval * ([self.linesArr[0] dataArr].count - 1);
NSArray *touchesArr = [event allTouches].allObjects;
// 单指拖动时,改变折线图偏移量
if (1 == touchesArr.count) {
CGPoint currentTouchPoint = [[touches anyObject] locationInView:self];
CGPoint previousTouchPoint = [[touches anyObject] previousLocationInView:self];
self.contentOffset += previousTouchPoint.x - currentTouchPoint.x;
} else if (2 == touchesArr.count) {
// 双指缩放时,改变图表偏移量及相邻点间隔
CGPoint currentOnePoint = [touchesArr[0] locationInView:self];
CGPoint currentAnotherPoint = [touchesArr[1] locationInView:self];
CGPoint previousOnePoint = [touchesArr[0] previousLocationInView:self];
CGPoint previousAnotherPoint = [touchesArr[1] previousLocationInView:self];
CGFloat currentFingerSpacing = fabs(currentOnePoint.x - currentAnotherPoint.x);
CGFloat previousFingerSpacing = fabs(previousOnePoint.x - previousAnotherPoint.x);
CGFloat centerX = (currentOnePoint.x - currentAnotherPoint.x)/2 + currentAnotherPoint.x;
if (currentFingerSpacing > previousFingerSpacing && self.pointInterval < MAX_POINT_INTERVAL) {
// 放大
self.pointInterval *= 1.05;
self.contentOffset = self.contentOffset * 1.05 + (centerX - self.leftMargin) * 0.05;
} else if (currentFingerSpacing < previousFingerSpacing && self.pointInterval > MIN_POINT_INTERVAL) {
// 缩小
self.pointInterval *= 0.95;
self.contentOffset = self.contentOffset * 0.95 - (centerX - self.leftMargin) * 0.05;
}
}
// 控制折线图偏移极限
if (self.contentOffset < 0) {
self.contentOffset = 0;
} else if (self.contentOffset > lineLength) {
self.contentOffset = lineLength;
}
// 刷新折线图
[self setNeedsDisplay];
}
使用:
// ViewController.m
#import "ViewController.h"
#import "HTChartView.h"
@interface ViewController ()
@property (nonatomic, strong)HTChartView *chartView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 初始化图表
self.chartView = [[HTChartView alloc] initWithFrame:CGRectMake(0, 0, 320, 250)];
self.chartView.backgroundColor = [UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:1];
[self.chartView setCenter:CGPointMake([UIScreen mainScreen].bounds.size.width/2, [UIScreen mainScreen].bounds.size.height/2)];
self.chartView.maxValue = 10;
self.chartView.minValue = 0;
[self.view addSubview:self.chartView];
// 添加折线
[self addLines];
}
// 模拟折线数据
- (void)addLines {
HTLine *yellowLine = [[HTLine alloc] init];
[yellowLine.dataArr addObjectsFromArray:@[@(1), @(3), @(5), @(7), @(9), @(5), @(6), @(4), @(2), @(8), @(1), @(6), @(4), @(5), @(9), @(8), @(2)]];
yellowLine.lineColor = [UIColor yellowColor];
HTLine *redLine = [[HTLine alloc] init];
[redLine.dataArr addObjectsFromArray:@[@(2), @(4), @(5), @(8), @(6), @(1), @(7), @(5), @(3), @(4), @(6), @(2), @(8), @(7), @(9), @(5), @(2)]];
redLine.lineColor = [UIColor redColor];
NSArray *linesArr = @[yellowLine, redLine];
[self.chartView addLines:linesArr];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end