折线图实践2018

需求简介

类似支付宝那样的钱包工具,有很大一个类别理财,经常出现收益率曲线之类的,这个就是折线图,大概的样子如下

折线图实践2018_第1张图片
image.png

如何实现

  • 采用贝塞尔曲线自己画,采用CAShapeLayer进行组合,也是可以做的。只是有点复杂,操心的内容有点多。

  • 采用第三方库,这也是一个好主意。有现实需求,自己实现又烦,好用的第三方库就来了。经过比较,我们可以选择 Charts

  • 这个第三方库是用swift写的,在目前的object-c工程中使用有点麻烦。不过,为了和Android保持一致,这点麻烦算不了什么。

  • 这些纠结的过程,已经在另一篇文章里写了:关于图表库的选择

包装Charts

Charts中代表折线图的是LineChartView,从BarLineChartViewBase,ChartViewBase,NSUIView,UIView一路继承过来。

  • 创建一个内部属性成员LineChartView,作为我们的自定义view的子view,并且占满所有空间。我们的自定义view只是一个容器。
@interface KJTLineChartView ()

@property (strong, nonatomic) LineChartView *lineView;

@end
  • 创建子视图
#pragma mark - private
- (void)setup {
    self.lineView = [[LineChartView alloc] init];
    [self addSubview:self.lineView];
}
  • 自定义视图只是作为容器,将刚刚创建的折线图铺满整个空间。
// 布局
- (void)layoutSubviews {
    [self.lineView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.mas_equalTo(0);
        make.bottom.mas_equalTo(0);
        make.left.mas_equalTo(0);
        make.right.mas_equalTo(0);
    }];
}
  • 由于没有数据,所以现在是空白:
折线图实践2018_第2张图片
image.png
  • 图中的文案,在ChartViewBase中可以找到:
折线图实践2018_第3张图片
image.png

设置数据

  • 图中数据点的包含(x,y)两个维度的信息,数据类型都是double。在大多数的介绍文章中,都是把x轴的信息简化为序号index,这当然是可以的。

  • 这次的需求是一张“七日年化收益率”折线图,'y'轴是收益率,数据类型是double,没问题。横轴是七天的日期,是“月月-日日”的格式。考虑到NSDate类型转化为double和字符串都很方便,所以x轴数据可以直接是“时间戳”,也就是从1970年到现在所过的秒数。所以这次的做法是将NSDate类型转化为double,直接作为x轴数据来实现的。

  • 关于数据,设置过程主要有以下几步

  1. 需要封装成一个ChartDataEntry结构;
  2. 多个ChartDataEntry然后形成一个数组之后;
  3. 将数组再包装成一个LineChartDataSet结构,这个结构代表一条曲线;
  4. 将多个(包括1个)LineChartDataSet结构包装成一个数组,所以可以同时显示多条曲线;
  5. 将这个数组包装成LineChartData结构;
  6. 用得到的这个结构,赋值给LineChartView的data成员;
  • 设计了一个对外接口,提供标签数组和y值数组
// x轴是日期,y轴是数据
- (void)updateWithDates:(NSArray *)dates values:(NSArray *)values {
    //
    // 日期和数据组成一个点,形成一条折线
    //
    // 1. 将日期和数据取出,包装成ChartDataEntry,组成新的数组
    // x轴是日期, y轴是数据
    NSMutableArray *entrys = [NSMutableArray array];
    NSInteger count = dates.count;
    for (NSInteger index = 0; index < count; index++) {
        double x = (double)dates[index].timeIntervalSince1970;
        double y = [values[index] doubleValue];
        ChartDataEntry *entry = [[ChartDataEntry alloc] initWithX:x y:y];
        [entrys addObject:entry];
    }
    // 2. 组装LineChartDataSet结构,并做相应的配置
    LineChartDataSet *dataSet = [[LineChartDataSet alloc] initWithValues:entrys label:nil];
    // 配置先不做,看默认效果
    // 3. 组装LineChartDataSet数组,这里只需要一个dataSet
    NSArray *dataSets = [NSArray arrayWithObject:dataSet];
    // 4. 组装LineChartData结构
    LineChartData *data = [[LineChartData alloc] initWithDataSets:dataSets];
    // 5. 赋值
    self.lineView.data = data;
}
  • 在调用的ViewController中,给几个测试数据
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    NSDate *today = [NSDate date];
    NSArray *dates = @[today, [today kjtDateByAddingDays:1], [today kjtDateByAddingDays:2], [today kjtDateByAddingDays:3], [today kjtDateByAddingDays:4], [today kjtDateByAddingDays:5], [today kjtDateByAddingDays:6]];
    NSArray *values = @[@3.729, @3.683, @2.8, @3.1, @4.123, @3.587, @3.7482];
    [self.lineChartView updateWithDates:dates values:values];
}

运行起来之后,效果是这样的:

折线图实践2018_第4张图片
image.png

整体设置

默认的可以拖动,可以放大,双击也可以放大,y轴有两个。在这里,这些都不需要,可以做如下配置:

    //
    // 设置chart view 整体
    //
    // 选中处理等
    self.lineView.delegate = self;
    // 隐藏说明图标
    self.lineView.legend.form = ChartLegendFormNone;
    // 无数据提示信息
    self.lineView.noDataText = @"暂无数据";
    // 取消Y轴缩放
    self.lineView.scaleYEnabled = NO;
    // 取消X轴缩放
    self.lineView.scaleXEnabled = NO;
    // 取消双击缩放
    self.lineView.doubleTapToZoomEnabled = NO;
    // 取消拖拽图标
    self.lineView.dragEnabled = NO;
    // 加载动画
    [self.lineView animateWithXAxisDuration:1.0];
    // 隐藏右Y轴
    self.lineView.rightAxis.enabled = NO;

加上这些配置之后,右y轴没了,也不能放大了,legend也没有了,并且还有生成动画。大致效果如下:

折线图实践2018_第5张图片
image.png

配置左Y轴

根据设计UI图,配置左Y轴的相关属性

    //
    // 设置左Y轴
    //
    // y轴展示多少个
    self.lineView.leftAxis.labelCount = 6;
    // 设置Y轴的最小值
    self.lineView.leftAxis.axisMinimum = 0;
    // Y轴颜色
    self.lineView.leftAxis.axisLineColor = kColorWithHexA(0x000000,  0.24);
    // 文字颜色
    self.lineView.leftAxis.labelTextColor = kColorWithHexA(0x000000,  0.24);
    // 文字字体
    self.lineView.leftAxis.labelFont = kFontSystem10;
    // 网格线颜色
    self.lineView.leftAxis.gridColor = kColorWithHex(0xECECF1);

这时的效果如下:

折线图实践2018_第6张图片
image.png

配置X轴

    //
    // 设置X轴
    //
    // 设置x轴数据在底部
    self.lineView.xAxis.labelPosition = XAxisLabelPositionBottom;
    // x轴不画网格线
    self.lineView.xAxis.drawGridLinesEnabled = NO;
    // X轴颜色
    self.lineView.xAxis.axisLineColor = kColorWithHexA(0x000000,  0.24);
    // 文字颜色
    self.lineView.xAxis.labelTextColor = kColorWithHexA(0x000000,  0.24);
    // 文字字体
    self.lineView.xAxis.labelFont = kFontSystem10;
折线图实践2018_第7张图片
image.png

配置折线

    // 是否在拐点处显示数据
    dataSet.drawValuesEnabled = NO;
    // 折线宽度
    dataSet.lineWidth = 2;
    // 折线颜色
    [dataSet setColor:kRedColor];
    // 是否绘制拐点
    dataSet.drawCirclesEnabled = NO;
    // 是否填充颜色
    dataSet.drawFilledEnabled = YES;
    // 填充颜色
    dataSet.fillColor = kRedColor;
    // 填充颜色透明度
    dataSet.fillAlpha = 0.20;
    // 去掉左下角“DataSet”字符串
    dataSet.label = nil;
折线图实践2018_第8张图片
image.png

格式化坐标轴标签

标签格式的设置比较特殊,需要一个单独的类,实现IChartAxisValueFormatter协议。只有一个函数

折线图实践2018_第9张图片
image.png
  • Y轴格式化,将double类型的数据,转化为3位小数的字符串。并且为了避免拥挤,0不显示
@implementation KJTLabelFormatterY

#pragma mark - IChartAxisValueFormatter
- (NSString *)stringForValue:(double)value axis:(ChartAxisBase *)axis {
    // 第一个Y轴坐标不显示,避免和第一个X坐标拥挤
    if (value == 0) {
        return @"";
    }
    return [NSString stringWithFormat:@"%0.3f", value];
}

@end

配置的地方,指定这个自定义的类

    // 格式化y标签
    self.lineView.leftAxis.valueFormatter = [[KJTLabelFormatterY alloc] init];
折线图实践2018_第10张图片
image.png
  • X轴格式化,这里就很简单,将时间戳转换为“月月-日日”格式的日期字符串就可以了。
@implementation KJTLabelFormatterX

#pragma mark - IChartAxisValueFormatter
- (NSString *)stringForValue:(double)value axis:(ChartAxisBase *)axis {
    NSDate *date = [NSDate dateWithTimeIntervalSince1970:(NSTimeInterval)value];
    return [date kjtStringWithFormat:@"MM-dd"];
}

@end

配置的地方:

    // 格式化x轴标签
    self.lineView.xAxis.valueFormatter = [[KJTLabelFormatterX alloc] init];
折线图实践2018_第11张图片
image.png

设置选中标签

提供了一个ChartMarkerView视图,作为选中之后弹出的“气泡视图”。这个可以看做一个容器,具体的样子,完全可以自定义。这里,就用了一个UILabel和一个UIImageView来实现。
很多文章中提到的BalloonMarker并不存在,这是官方demo中自己写的一个自定义视图,并且是swift的,不能直接用。
“气泡视图”ChartMarkerView的原点,默认在选中数据点上,效果会出现在下方。如果要做到UI上那样在选中点上方,需要通过offset属性来调节位置。这个地方相对布局不方便,直接用frame比较好。这个过程相对比较繁琐,需要耐心调整。

    //
    // 设置选中时候的"气泡视图"
    //
    // 这个标记视图将以数据点为起点布局
    ChartMarkerView *markerView = [[ChartMarkerView alloc] init];
    // 显示y轴数据的标签
    self.markerLabel = [[UILabel alloc] init];
    self.markerLabel.layer.cornerRadius = 9;
    self.markerLabel.layer.masksToBounds = YES;
    self.markerLabel.font = kFontSystem12;
    self.markerLabel.textAlignment = NSTextAlignmentCenter;
    self.markerLabel.textColor = [UIColor whiteColor];
    self.markerLabel.backgroundColor = kRedColor;
    [markerView addSubview:self.markerLabel];
    // 选中小图标
    UIImageView *imageView = [[UIImageView alloc] init];
    imageView.image = [UIImage imageNamed:@"折线图选中"];
    [markerView addSubview:imageView];
    // 布局,标签和图标右对齐,间隔为4
    self.markerLabel.frame = CGRectMake(0, 0, 49, 18);
    imageView.frame = CGRectMake(49-14, 18+4, 14, 14);
    // 设置偏移量,使数据点和小图标的中心点重合
    CGFloat x = imageView.center.x;
    CGFloat y = imageView.center.y;
    markerView.offset = CGPointMake(-x, -(y+7));
    // 设置折线图的标签视图
    markerView.chartView = self.lineView;
    self.lineView.marker = markerView;

UILabel上要显示y的值,需要实现ChartViewDelegate协议

#pragma mark - ChartViewDelegate
- (void)chartValueSelected:(ChartViewBase *)chartView entry:(ChartDataEntry *)entry highlight:(ChartHighlight *)highlight {
    self.markerLabel.text = [NSString stringWithFormat:@"%0.4f", entry.y];
}

效果图如下,和UI设计图比较接近了。

折线图实践2018_第12张图片
image.png
  • 在顶部,如果空间不够,由于受到边界影响,气泡视图会出现移位现象,导致图片对不准。
折线图实践2018_第13张图片
image.png
  • 1个点,由于有坐标轴的影响,位置也会偏移一些
折线图实践2018_第14张图片
image.png
  • UI图上是"山顶",效果相对较好,如果位置出现在"山谷",效果就差一点了
折线图实践2018_第15张图片
image.png

小结

相对于自己来写,省力很多,整体效果也好很多。虽然配置比较麻烦,思路有点奇特。推荐使用。

参考文章

  • iOS Charts框架集成及使用

  • iOS-Charts看这个就够了

  • iOS使用Charts框架绘制折线图

  • ios-charts之折线图

你可能感兴趣的:(折线图实践2018)