本篇介绍如何使用CoreGraphics绘制柱状图、圆饼图、环形图以及区域填充
有了前两篇的基础,绘制后面这些图表就简单的非常了。在第一篇的绘图三板斧中介绍了所有绘制图表所需的必要技巧:
绘制折线图:就是三板斧中的画一条线,图表中多画了几条线段而已
绘制柱状图:就是三板斧中绘制一个矩形和填充一个矩形,图表中多画了几个矩形,多填充了几个矩形区域
绘制圆饼图:就是三板斧中的画一个又一个扇形,然后填充,最后找到准确的位置写上标题
绘制环形图:这个就更简单了,就是画几段圆弧,把圆弧的宽度加大,就成了圆环
绘制区域填充:把画的线闭合,然后填充,就行了
这几种图表过于简单,所以在这最后一篇集中来写了,一口气就都说完吧。
柱状图
柱状图需要显示坐标系,所以要继承自 CoordinateSystem
。然后要给柱状图的类起个名字,就叫 BarChart
,别问我为什么叫这个,有道词典说得这么起名。柱状图的数据类也要起个名字,就叫 BarChartData
吧,这样有辨识度。
先统一一下思想, BarChart
代表的是这张柱状图, BarChartData
保存的是每一根 “柱体” 的具体信息
ok,两个类建立好了之后,要思考一下都有什么属性或者方法。先看 BarChart
类。
#import "CoordinateSystem.h"
@class BarChartData;
@interface BarChart : CoordinateSystem
@property (nonatomic, strong) NSMutableArray<BarChartData *> *dataArray;
@property (nonatomic, assign) CGFloat barSpacing;
@property (nonatomic, assign) CGFloat barLeftInset;
@property (nonatomic, assign) CGFloat barRightInset;
@end
柱状图 BarChart
需要有一个数据集合 NSMutableArray<BarChartData *> *dataArray
,用来保存将要绘制的 n 个 “柱体” 的具体信息。然后就是一些常规的属性:
CGFloat barSpacing
柱体间距CGFloat barLeftInset
最左侧柱体与Y轴(坐标系左边界)的间距CGFloat barRightInset
最右侧柱体到坐标系右边界的距离
看完 BarChart
类,接下来看 BarChartData
类需要来点什么属性
#import <UIKit/UIKit.h>
@interface BarChartData : NSObject
@property (nonatomic, copy) NSString *valueForX;
@property (nonatomic, copy) NSString *valueForY;
@property (nonatomic, strong) UIColor *borderColor;
@property (nonatomic, strong) UIColor *fillColor;
@property (nonatomic, assign) CGFloat borderWidth;
- (instancetype)initWithValueForX:(NSString *)valueForX valueForY:(NSString *)valueForY;
@end
NSString *valueForX
X轴刻度NSString *valueForY
Y轴刻度UIColor *borderColor
柱体边框线颜色CGFloat borderWidth
柱体宽度UIColor *fillColor
柱体填充颜色
外加一个初始化方法,搞定!
准备工作做得差不多了,下面开始进入正题。
与折线图一样,需要先确定坐标系。Y轴不变,正常绘制纬线和刻度,先计算取值范围,就是上一篇中的三步走,把最大值和最小值一通修改得到的取值范围,用这个取值范围来确定Y轴的刻度。
X轴需要重写一下绘制刻度的方法,用子类方法覆盖父类方法的方式,就不用再自己调用一遍了。柱状图的效果图大家也看到了,之所以重写是因为柱状图需要分别绘制上下标题(刻度)。柱体上方的标题是它的Y轴刻度,下方的是它的X轴刻度。
在 BarChart
类的 drawRect:
方法中:
- (void)drawRect:(CGRect)rect {
// 计算Y轴取值范围
[self calcValueRangeForY];
[self initAxisY];
[self initAxisX];
// 初始化父类 drawRect
[super drawRect:rect];
}
initAxisY
方法和 initAxisX
方法与折线图中的思路完全一致,目的还是要确定取值范围,然后确定Y轴刻度和X轴刻度从而绘制坐标系。不过这里的坐标系不需要经线,CoordinateSystem
中经线默认是不显示的,所以只要不给赋值YES就行了。
/**
显示经线
*/
@property (nonatomic, assign) BOOL displayLongitude;
之后就是父类的 drawRect:
方法开始执行,绘制坐标系。坐标系画完后会调用预留接口 drawData:
方法,在重写的 drawData:
方法中可以绘制柱体:
- (void)drawData:(CGRect)rect {
// 绘制数据
[self drawBarChart:rect];
}
#pragma mark - 绘制柱体
- (void)drawBarChart:(CGRect)rect {
CGFloat barWidth = (rect.size.width - self.axisMarginLeft - self.axisMarginRight - self.barLeftInset - self.barRightInset - self.barSpacing * (self.dataArray.count - 1)) / self.dataArray.count;
CGContextRef context = UIGraphicsGetCurrentContext();
if (self.dataArray && self.dataArray.count) {
CGFloat valueX = self.axisMarginLeft + self.barLeftInset;
for (BarChartData *barData in self.dataArray) {
if (barData) {
CGFloat valueY = (1 - (barData.valueForY.floatValue - self.minValue)/(self.maxValue - self.minValue)) * (rect.size.height - self.axisMarginBottom - self.axisMarginTop) + self.axisMarginTop;
CGRect drawFrame = CGRectMake(valueX, valueY, barWidth, rect.size.height - valueY - self.axisMarginBottom);
CGContextSetLineWidth(context, barData.borderWidth);
CGContextSetStrokeColorWithColor(context, barData.borderColor.CGColor);
CGContextSetFillColorWithColor(context, barData.fillColor.CGColor);
CGContextFillRect(context, drawFrame);
CGContextStrokeRect(context, drawFrame);
}
//X位移
valueX = valueX + self.barSpacing + barWidth;
}
}
}
在 BarChart
类中需要重写父类的绘制标题方法:
#pragma mark - 绘制柱条上下标题
- (void)drawXAxisTitles:(CGRect)rect {
if ([self.longitudeTitles count] <= 0) {
return;
}
CGFloat barWidth = (rect.size.width - self.axisMarginLeft - self.axisMarginRight - self.barLeftInset - self.barRightInset - self.barSpacing * (self.dataArray.count - 1)) / self.dataArray.count;
CGFloat barCenterX = self.axisMarginLeft + self.barLeftInset + barWidth/2;
for (int i = 0; i < [self.longitudeTitles count]; i++) {
// 取出上下标题
BarChartData *barData = self.dataArray[i];
NSString *topTitle = [self formatYTitles:barData.valueForY.longLongValue];
NSString *bottomTitle = (NSString *) [self.longitudeTitles objectAtIndex:i];
// 统一设置属性
UIFont *textFont= self.longitudeFont; //设置字体
NSMutableParagraphStyle *textStyle=[[NSMutableParagraphStyle alloc]init];//段落样式
textStyle.lineBreakMode = NSLineBreakByWordWrapping;
NSDictionary *attrs = @{NSFontAttributeName:textFont,
NSParagraphStyleAttributeName:textStyle,
NSForegroundColorAttributeName:self.longitudeFontColor};
// 绘制下标题
CGSize bottomTitleSize = [bottomTitle boundingRectWithSize:CGSizeMake(100, 100)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:attrs
context:nil].size;
CGRect bottomTitleRect= CGRectMake(barCenterX - bottomTitleSize.width/2, rect.size.height - self.axisMarginBottom, bottomTitleSize.width, bottomTitleSize.height);
textStyle.alignment=NSTextAlignmentCenter;
[bottomTitle drawInRect:bottomTitleRect withAttributes:attrs];
// 绘制上标题
CGSize topTitleSize = [topTitle boundingRectWithSize:CGSizeMake(100, 100)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:attrs
context:nil].size;
CGFloat valueY = (1 - (barData.valueForY.floatValue - self.minValue)/(self.maxValue - self.minValue)) * (rect.size.height - self.axisMarginBottom - self.axisMarginTop) + self.axisMarginTop - topTitleSize.height;
CGRect topTitleRect= CGRectMake(barCenterX - topTitleSize.width/2, valueY, topTitleSize.width, topTitleSize.height);
textStyle.alignment=NSTextAlignmentCenter;
[topTitle drawInRect:topTitleRect withAttributes:attrs];
// X位移
barCenterX = barCenterX + self.barSpacing + barWidth;
}
}
- (NSString *)formatYTitles:(long)value {
if (value >= 10000) {
return [NSString stringWithFormat:@"%.2f 万", value/10000.0f];
}
else {
return [NSString stringWithFormat:@"%ld", value];
}
}
最后为表示对我大秦帝国历代君王的追思,我特意把十字交叉线的标题做了完善,之前折线图中的十字交叉线的标题都是显示的百分比,没有具体完善。代码如下:
先在 CoordinateSystem
类中搞一个协议,为的是让子类可以自定义十字交叉线的标题,子类可以返回一个只有两个元素的数组,数组的第一个元素是十字交叉线X轴标题,数组的最后一个元素是十字交叉线Y轴标题。
@protocol CoordinateSystemDelegate <NSObject>
- (NSArray *)crossLineTouchPoint:(CGPoint)touchPoint xPercent:(CGFloat)xPercent yPercent:(CGFloat)yPercent frame:(CGRect)rect;
@end
然后修改一下 CoordinateSystem
类中获取十字交叉线XY轴刻度的方法,只要子类实现代理方法并返回标题,十字交叉线的XY轴刻度就会按照返回值显示:
// 获取十字交叉线的X轴刻度
- (NSString *)calcAxisXGraduate:(CGRect)rect {
if ([self.delegate respondsToSelector:@selector(crossLineTouchPoint:xPercent:yPercent:frame:)]) {
NSArray *titles = [self.delegate crossLineTouchPoint:self.singleTouchPoint xPercent:[self touchPointAxisXValue:rect] yPercent:[self touchPointAxisYValue:rect] frame:rect];
return titles.firstObject;
}
return [NSString stringWithFormat:@"%f", [self touchPointAxisXValue:rect]];
}
// 获取十字交叉线的Y轴刻度
- (NSString *)calcAxisYGraduate:(CGRect)rect {
if ([self.delegate respondsToSelector:@selector(crossLineTouchPoint:xPercent:yPercent:frame:)]) {
NSArray *titles = [self.delegate crossLineTouchPoint:self.singleTouchPoint xPercent:[self touchPointAxisXValue:rect] yPercent:[self touchPointAxisYValue:rect] frame:rect];
return titles.lastObject;
}
return [NSString stringWithFormat:@"%f", [self touchPointAxisYValue:rect]];
}
然后回到柱状图这里,实现代理,确定十字交叉线的XY轴标题(刻度)。这里进行了柱间区域的判断,比如 “嬴稷” 和 “嬴政” 之间的留空区是不显示X轴标题的,因为那部分本来就没有X轴对应的标题,就是一块空白。
#pragma mark - 十字交叉线重绘标题
- (NSArray *)crossLineTouchPoint:(CGPoint)touchPoint xPercent:(CGFloat)xPercent yPercent:(CGFloat)yPercent frame:(CGRect)rect {
NSMutableArray *arrM = [NSMutableArray array];
// 处理X轴
CGFloat barWidth = (rect.size.width - self.axisMarginLeft - self.axisMarginRight - self.barLeftInset - self.barRightInset - self.barSpacing * (self.dataArray.count - 1)) / self.dataArray.count;
CGFloat barLeft = self.axisMarginLeft + self.barLeftInset;
for (int i = 0; i < self.dataArray.count; i++) {
CGFloat barRight = barLeft + barWidth;
if (touchPoint.x >= barLeft && touchPoint.x < barRight) {
BarChartData *data = self.dataArray[i];
[arrM addObject:data.valueForX];
break;
}
barLeft = barLeft + barWidth + self.barSpacing;
}
if (arrM.count == 0) {
[arrM addObject:@""];
}
// 处理Y轴
CGFloat valueRange = self.maxValue - self.minValue;
CGFloat valueY = valueRange * yPercent;
NSString *yString = [self formatYTitles:valueY];
[arrM addObject:yString];
return arrM;
}
OK! 柱状图大功告成
圆饼图
绘制圆饼图不需要显示坐标系,所以圆饼图只需继承 UIView
。给它也取个名字,就叫 PieChart
。圆饼图的数据类也取个名字,叫 PieChartData
,一个非常优雅的名字。
看看圆饼图都需要哪些属性:
#import <UIKit/UIKit.h>
@class PieChartData;
@interface PieChart : UIView
@property (nonatomic, strong) NSMutableArray<PieChartData *> *dataArray;
/**
边框线宽
*/
@property (nonatomic, assign) CGFloat borderWidth;
/**
边框线颜色
*/
@property (nonatomic, strong) UIColor *borderColor;
/**
标题颜色
*/
@property (nonatomic, strong) UIColor *titleColor;
/**
标题字号
*/
@property (nonatomic, strong) UIFont *titleFont;
@end
再看看圆饼图数据类需要哪些属性:
#import <UIKit/UIKit.h>
@interface PieChartData : NSObject
@property (nonatomic, copy) NSString *title;
@property (nonatomic, assign) CGFloat value;
@property (nonatomic, strong) UIColor *fillColor;
- (instancetype)initWithValue:(CGFloat)value fillColor:(UIColor *)fillColor title:(NSString *)title;
@end
NSString *title
每个扇形区域的标题CGFloat value
每个扇形区域的数值UIColor *fillColor
每个扇形区域的填充色,这个属性放在PieChart
中也可以,看具体需求而定
回到 PieChart
类中,先初始化各个属性,给定默认值:
// 给定圆周率 π 的值
#define PI 3.141592653f
- (id)init {
self = [super init];
if (self) {
//初始化属性
[self initProperty];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
//初始化属性
[self initProperty];
}
return self;
}
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.frame = frame;
//初始化属性
[self initProperty];
}
return self;
}
- (void)initProperty {
self.borderColor = [UIColor whiteColor];
self.borderWidth = 1;
self.titleFont = [UIFont systemFontOfSize:14];
self.titleColor = [UIColor blackColor];
}
画圆饼图
- (void)drawRect:(CGRect)rect {
if (!self.dataArray || !self.dataArray.count) {
return;
}
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, self.borderWidth);
CGContextSetAllowsAntialiasing(context, YES);
// 数据总和
CGFloat total = [self calcTotalValue];
// 起始位置弧度
CGFloat offset = PI * -0.5;
// 半径和圆心
CGFloat radius = MIN(rect.size.width, rect.size.height) / 2;
CGPoint circleCenter = CGPointMake(rect.size.width/2, rect.size.height/2);
for (PieChartData *data in self.dataArray) {
if (data) {
/*
填充扇形区域
*/
// 拿出数据
CGFloat value = data.value;
// 计算百分比
CGFloat percent = value / total;
// 确定分支弧度
CGFloat endAngle = percent * 2 * PI;
// 设定起始点为圆心
CGContextMoveToPoint(context, circleCenter.x, circleCenter.y);
// 添加一个圆
CGContextAddArc(context, circleCenter.x, circleCenter.y, radius, offset, endAngle + offset, 0);
// 设置填充色
CGContextSetFillColorWithColor(context, data.fillColor.CGColor);
// 填充
CGContextFillPath(context);
/*
绘制扇形区域的边框线
*/
// 设定起始点为圆心
CGContextMoveToPoint(context, circleCenter.x, circleCenter.y);
// 添加一个圆弧
CGContextAddArc(context, circleCenter.x, circleCenter.y, radius, offset, endAngle + offset, 0);
// 关闭路径
CGContextClosePath(context);
// 设置画笔颜色
CGContextSetStrokeColorWithColor(context, self.borderColor.CGColor);
// 画线
CGContextStrokePath(context);
/*
绘制标题和百分比
*/
// 一半的弧度
CGFloat halfAngle = offset + endAngle/2;
// 到圆心的距离
CGFloat farFromCircleCenter = radius*0.7;
CGFloat X = circleCenter.x + farFromCircleCenter * cos(halfAngle);
CGFloat Y = circleCenter.y + farFromCircleCenter * sin(halfAngle);
NSMutableParagraphStyle *textStyle = [[NSMutableParagraphStyle alloc] init];//段落样式
textStyle.alignment = NSTextAlignmentCenter;
textStyle.lineBreakMode = NSLineBreakByWordWrapping;
NSDictionary *attrs = @{NSFontAttributeName:self.titleFont,
NSParagraphStyleAttributeName:textStyle,
NSForegroundColorAttributeName:self.titleColor};
// 拼接 标题 和 百分比
NSString *text = [NSString stringWithFormat:@"%@\n%.2f%%", data.title, percent*100];
CGSize titleSize = [text boundingRectWithSize:CGSizeMake(100, 100) options:NSStringDrawingUsesLineFragmentOrigin attributes:attrs context:nil].size;
CGRect textRect= CGRectMake(X - titleSize.width / 2, Y - titleSize.height / 2, titleSize.width, titleSize.height);
[text drawInRect:textRect withAttributes:attrs];
// 弧度积累偏移
offset += endAngle;
}
}
}
- (CGFloat)calcTotalValue {
if (self.dataArray && self.dataArray.count) {
CGFloat sum = 0;
for (PieChartData *data in self.dataArray) {
sum += data.value;
}
return sum;
}
return 0;
}
说一下思路:
原作《Cocoa-Charts》中绘制圆饼图的代码中有很多不必要的代码我全部给删掉了。比如填充时不需要设定线宽也不需要设定画笔颜色,或者填充时不需要闭合路径,因为填充时自动闭合路径,写了也白写。
首先,画圆弧的起始弧度定在了(-π/2),请看下图来感受一下(-π/2)在哪:计算全部分支的数值总和,然后计算每个分支的数值在总和中的占比,这个比例就是此分支在整个圆中要占的比例,用这个比例值乘以 2π 来确定它的弧度大小。
先填充扇形再画扇形边框线,这些步骤都很简单。重点是标题的位置,要怎么放才会真的居中?
原作中的标题位置有点尴尬,并没有做到真正居中,让我这个强迫症患者看着很是难受。所以在标题位置这里重新做了一下优化。
我画了一个示意图,我理想中的标题位置如图所示:
绿色虚线是这一分支圆弧的一半弧度,即该圆弧的中心弧度线
CGFloat halfAngle = offset + endAngle/2;
红色实线的长度是到圆心 70%半径 的距离
CGFloat farFromCircleCenter = radius*0.7;
确定了长度和弧度,就有了我们要的点,即文字的中心点,图中的黑色圆点
两条蓝色实线和红色实线形成了直角三角形,就是该中心点用来计算XY坐标的三角函数示意
文字标题的中心点 在该扇形中心弧度线上 距离圆心 70%半径 的位置。
CGFloat X = circleCenter.x + farFromCircleCenter * cos(halfAngle);
CGFloat Y = circleCenter.y + farFromCircleCenter * sin(halfAngle);
这样文字的位置可以根据代码 radius*0.7
中的比例来设置文字距离圆心的距离。
圆饼图画完喽!!!
环形图
环形图要继承自圆饼图,因为两者基本是一个思路,用的属性和数据都一样。没什么好说的,看懂圆饼图就明白这个是怎么来的,不比比,直接上代码
#import "PieChart.h"
@interface AnnulusChart : PieChart
/**
* 圆环宽度占半径的多少比例
*
* 取值范围 0-1
*/
@property (nonatomic, assign) CGFloat annulusWidthPercentToRadius;
@end
#import "AnnulusChart.h"
#import "PieChartData.h"
#define PI 3.141592653f
@implementation AnnulusChart
- (void)initProperty {
[super initProperty];
self.annulusWidthPercentToRadius = 0.3;
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetAllowsAntialiasing(context, YES);
CGContextSetStrokeColorWithColor(context, self.borderColor.CGColor);
if (self.dataArray && self.dataArray.count && self.annulusWidthPercentToRadius > 0 && self.annulusWidthPercentToRadius < 1) {
// 数据总和
CGFloat total = [self calcTotalValue];
// 起始位置弧度
CGFloat offset = PI * -0.5;
// 半径和圆心
CGFloat radius = MIN(rect.size.width, rect.size.height) / 2;
CGPoint circleCenter = CGPointMake(rect.size.width/2, rect.size.height/2);
// 遍历每一条数据列表
for (int j = 0; j < [self.dataArray count]; j++) {
PieChartData *entity = [self.dataArray objectAtIndex:j];
//角度
CGFloat sweep = entity.value * 2 * PI / total;
// 边线绘制的宽度是圆周内一半外一半
CGContextSetLineWidth(context, radius * self.annulusWidthPercentToRadius);
CGContextSetStrokeColorWithColor(context, entity.fillColor.CGColor);
CGContextAddArc(context, circleCenter.x, circleCenter.y, radius * (1 - self.annulusWidthPercentToRadius / 2), offset, offset + sweep, 0);
CGContextStrokePath(context);
//调整偏移
offset = offset + sweep;
}
}
}
@end
思路还是圆饼图的思路,就是这个 “环” 需要考虑一下。
原作者的做法就是上面代码中的实现方式,圆环用很粗的边框线来实现。这个边框线的宽度就直接用属性 annulusWidthPercentToRadius
乘以半径来定。
边框线如果很宽你就会看到,边框线不是全部绘制在圆周之外,而是一半在圆周内,一半在圆周外。下图白色实线就是圆周线实际的中心位置
还有一种办法就是画一个圆饼图,和前面的圆饼图完全一样,然后在画一个与底色相同的圆覆盖上。这样的好处就是可以有边框线,圆环的分界线会更明显。具体怎么实现就看你的需求了。
区域填充
区域填充就是在画折线图,需要填充的部分把它们的路径闭合进行填充就可以了。
这个填充需要注意两点
要点一
就是在第一篇文章提到的:CGContextFillPath
填充最多可以两条线的路径实现闭合,三条线的路径就失灵了。
什么意思? 请看一条线示意图:
上图给出起点,然后通过三次调用 CGContextAddLineToPoint
画了三条线段,从而连接成一条线的路径,这三个线段连成的线路径是 “一条线的路径”!!!这不是三条线而是一条线!!!
因为只有一条线,没有超过两条线,所以可以进行填充。此时填充这条路径的话会自动闭合起始点和终点,填充的是下图这个区域:
两条线的示意图:
上图中是两条线组成的闭合区域,也可以进行填充,填充的就是上图所示的闭合区域。
如果是下图这样的情况,也会自动闭合:
要点二
两个不相干的封闭路径都可以被一次性正常填充,比如多个矩形
上图这两个路径可以一次性进行填充
说了这么多的前提,那到底我在文章最前面给出的区域填充效果图是怎么填充的呢?
原作者并不是如我所想,使用要点一在两条线之间的区域一次性填充。而是画了无数个闭合的矩形,使用要点二进行一次性多个填充。把代码修改一下就可以再现原作者的思路,我们不填充而是进行画线:
这就是原作者的思路。
.h
文件
#import "LineChart.h"
@interface AreaChart : LineChart
@end
.m
文件
#import "AreaChart.h"
#import "LineData.h"
#import "LinePointData.h"
@implementation AreaChart
- (void)drawData:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 1);
CGContextSetAllowsAntialiasing(context, YES);
if (self.linesArray != NULL) {
// 经线间距
CGFloat longitudeSpacing = 0;
// 坐标
__block CGFloat valueX = 0;
__block CGFloat lastX = 0;
__block CGFloat lastY = 0;
// 逐条绘制
for (LineData *line in self.linesArray) {
if (!line || !line.linePointsDataArray || line.linePointsDataArray.count < 2) continue;
// 配置线条
CGContextSetStrokeColorWithColor(context, line.lineColor.CGColor);
CGContextSetLineWidth(context, line.lineWidth);
longitudeSpacing = (rect.size.width - self.axisMarginLeft - self.axisMarginRight) / (line.linePointsDataArray.count - 1);
valueX = self.axisMarginLeft;
[line.linePointsDataArray enumerateObjectsUsingBlock:^(LinePointData * _Nonnull point, NSUInteger idx, BOOL * _Nonnull stop) {
// 计算点的Y坐标
CGFloat valueY = (1 - (point.valueForY.floatValue - self.minValue) / (self.maxValue - self.minValue)) * (rect.size.height - self.axisMarginTop - self.axisMarginBottom) + self.axisMarginTop;
// 第一个初始点不画线
if (idx == 0) {
CGContextMoveToPoint(context, valueX, valueY);
lastY = valueY;
}
else {
CGContextAddLineToPoint(context, valueX, valueY);
lastY = valueY;
}
// X坐标移动
valueX = valueX + longitudeSpacing;
}];
// 绘制路径
CGContextStrokePath(context);
}
LineData *line1 = [self.linesArray objectAtIndex:0];
LineData *line2 = [self.linesArray objectAtIndex:1];
if (line1 != NULL && line2 != NULL) {
//设置线条颜色
CGContextSetStrokeColorWithColor(context, [UIColor greenColor].CGColor);
//获取线条数据
NSArray *line1Points = line1.linePointsDataArray;
NSArray *line2Points = line2.linePointsDataArray;
// 点线距离
CGFloat lineLength = ((rect.size.width - self.axisMarginLeft - self.axisMarginRight) / ([line1.linePointsDataArray count] - 1));
//起始点
valueX = super.axisMarginLeft;
//遍历并绘制线条
for (int j = 0; j < [line1Points count]; j++) {
LinePointData *line1Point = [line1Points objectAtIndex:j];
LinePointData *line2Point = [line2Points objectAtIndex:j];
//获取终点Y坐标
CGFloat valueY1 = (1 - (line1Point.valueForY.floatValue - self.minValue) / (self.maxValue - self.minValue)) * (rect.size.height - self.axisMarginTop - self.axisMarginBottom) + self.axisMarginTop;
CGFloat valueY2 = (1 - (line2Point.valueForY.floatValue - self.minValue) / (self.maxValue - self.minValue)) * (rect.size.height - self.axisMarginTop - self.axisMarginBottom) + self.axisMarginTop;
//绘制线条路径
if (j == 0) {
CGContextMoveToPoint(context, valueX, valueY1);
CGContextAddLineToPoint(context, valueX, valueY2);
CGContextMoveToPoint(context, valueX, valueY1);
} else {
CGContextAddLineToPoint(context, valueX, valueY1);
CGContextAddLineToPoint(context, valueX, valueY2);
CGContextAddLineToPoint(context, lastX, lastY);
CGContextMoveToPoint(context, valueX, valueY1);
}
lastX = valueX;
lastY = valueY2;
//X位移
valueX = valueX + lineLength;
}
CGContextClosePath(context);
CGContextSetAlpha(context, 0.5);
CGContextSetFillColorWithColor(context, [UIColor yellowColor].CGColor);
CGContextFillPath(context);
// CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
// CGContextStrokePath(context);
}
}
}
- (void)initAxisX {
[super initAxisX];
}
- (void)initAxisY {
[super initAxisY];
}
@end
我们还可以使用 要点一 来填充,左右两条竖线和第一条线连接在一起,第二条线和第一条线封闭。