iOS可复用控件之折线图

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



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值