本篇介紹如何使用CoreGraphics繪制柱狀圖、圓餅圖、環(huán)形圖以及區(qū)域填充
有了前兩篇的基礎亭饵,繪制后面這些圖表就簡單的非常了。在第一篇的繪圖三板斧中介紹了所有繪制圖表所需的必要技巧:
繪制折線圖:就是三板斧中的畫一條線换途,圖表中多畫了幾條線段而已
繪制柱狀圖:就是三板斧中繪制一個矩形和填充一個矩形劲厌,圖表中多畫了幾個矩形竖哩,多填充了幾個矩形區(qū)域
繪制圓餅圖:就是三板斧中的畫一個又一個扇形,然后填充脊僚,最后找到準確的位置寫上標題
繪制環(huán)形圖:這個就更簡單了相叁,就是畫幾段圓弧,把圓弧的寬度加大辽幌,就成了圓環(huán)
繪制區(qū)域填充:把畫的線閉合增淹,然后填充,就行了
這幾種圖表過于簡單乌企,所以在這最后一篇集中來寫了虑润,一口氣就都說完吧。
柱狀圖
柱狀圖需要顯示坐標系加酵,所以要繼承自 CoordinateSystem
拳喻。然后要給柱狀圖的類起個名字,就叫 BarChart
猪腕,別問我為什么叫這個冗澈,有道詞典說得這么起名。柱狀圖的數據類也要起個名字陋葡,就叫 BarChartData
吧亚亲,這樣有辨識度。
先統(tǒng)一一下思想腐缤, 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 個 “柱體” 的具體信息巾兆。然后就是一些常規(guī)的屬性:
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:
方法開始執(zhí)行湾碎,繪制坐標系。坐標系畫完后會調用預留接口 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];
// 統(tǒng)一設置屬性
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
類中搞一個協(xié)議,為的是讓子類可以自定義十字交叉線的標題丹墨,子類可以返回一個只有兩個元素的數組廊遍,數組的第一個元素是十字交叉線X軸標題,數組的最后一個元素是十字交叉線Y軸標題贩挣。
@protocol CoordinateSystemDelegate <NSObject>
- (NSArray *)crossLineTouchPoint:(CGPoint)touchPoint xPercent:(CGFloat)xPercent yPercent:(CGFloat)yPercent frame:(CGRect)rect;
@end
然后修改一下 CoordinateSystem
類中獲取十字交叉線XY軸刻度的方法喉前,只要子類實現(xiàn)代理方法并返回標題,十字交叉線的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]];
}
然后回到柱狀圖這里王财,實現(xiàn)代理卵迂,確定十字交叉線的XY軸標題(刻度)。這里進行了柱間區(qū)域的判斷绒净,比如 “嬴稷” 和 “嬴政” 之間的留空區(qū)是不顯示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
俐东,一個非常優(yōu)雅的名字。
看看圓餅圖都需要哪些屬性:
#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
每個扇形區(qū)域的標題CGFloat value
每個扇形區(qū)域的數值UIColor *fillColor
每個扇形區(qū)域的填充色订晌,這個屬性放在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) {
/*
填充扇形區(qū)域
*/
// 拿出數據
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);
/*
繪制扇形區(qū)域的邊框線
*/
// 設定起始點為圓心
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π 來確定它的弧度大小。
先填充扇形再畫扇形邊框線壳贪,這些步驟都很簡單陵珍。重點是標題的位置,要怎么放才會真的居中违施?
原作中的標題位置有點尷尬互纯,并沒有做到真正居中,讓我這個強迫癥患者看著很是難受磕蒲。所以在標題位置這里重新做了一下優(yōu)化留潦。
我畫了一個示意圖,我理想中的標題位置如圖所示:
綠色虛線是這一分支圓弧的一半弧度辣往,即該圓弧的中心弧度線
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
中的比例來設置文字距離圓心的距離。
圓餅圖畫完嘍R傺摺8匾薄街氢!
環(huán)形圖
環(huán)形圖要繼承自圓餅圖扯键,因為兩者基本是一個思路,用的屬性和數據都一樣珊肃。沒什么好說的荣刑,看懂圓餅圖就明白這個是怎么來的,不比比伦乔,直接上代碼
#import "PieChart.h"
@interface AnnulusChart : PieChart
/**
* 圓環(huán)寬度占半徑的多少比例
*
* 取值范圍 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
思路還是圓餅圖的思路厉亏,就是這個 “環(huán)” 需要考慮一下。
原作者的做法就是上面代碼中的實現(xiàn)方式烈和,圓環(huán)用很粗的邊框線來實現(xiàn)爱只。這個邊框線的寬度就直接用屬性 annulusWidthPercentToRadius
乘以半徑來定。
邊框線如果很寬你就會看到招刹,邊框線不是全部繪制在圓周之外恬试,而是一半在圓周內,一半在圓周外疯暑。下圖白色實線就是圓周線實際的中心位置
還有一種辦法就是畫一個圓餅圖训柴,和前面的圓餅圖完全一樣,然后在畫一個與底色相同的圓覆蓋上妇拯。這樣的好處就是可以有邊框線幻馁,圓環(huán)的分界線會更明顯。具體怎么實現(xiàn)就看你的需求了越锈。
區(qū)域填充
區(qū)域填充就是在畫折線圖仗嗦,需要填充的部分把它們的路徑閉合進行填充就可以了。
這個填充需要注意兩點
要點一
就是在第一篇文章提到的:CGContextFillPath
填充最多可以兩條線的路徑實現(xiàn)閉合甘凭,三條線的路徑就失靈了稀拐。
什么意思? 請看一條線示意圖:
上圖給出起點对蒲,然后通過三次調用 CGContextAddLineToPoint
畫了三條線段钩蚊,從而連接成一條線的路徑,這三個線段連成的線路徑是 “一條線的路徑”5赴E槁摺!這不是三條線而是一條線7耗瘛r鹋亍!
因為只有一條線,沒有超過兩條線刚操,所以可以進行填充闸翅。此時填充這條路徑的話會自動閉合起始點和終點,填充的是下圖這個區(qū)域:
兩條線的示意圖:
上圖中是兩條線組成的閉合區(qū)域菊霜,也可以進行填充坚冀,填充的就是上圖所示的閉合區(qū)域。
如果是下圖這樣的情況鉴逞,也會自動閉合:
要點二
兩個不相干的封閉路徑都可以被一次性正常填充记某,比如多個矩形
上圖這兩個路徑可以一次性進行填充
說了這么多的前提,那到底我在文章最前面給出的區(qū)域填充效果圖是怎么填充的呢构捡?
原作者并不是如我所想液南,使用要點一在兩條線之間的區(qū)域一次性填充。而是畫了無數個閉合的矩形勾徽,使用要點二進行一次性多個填充滑凉。把代碼修改一下就可以再現(xiàn)原作者的思路,我們不填充而是進行畫線:
這就是原作者的思路喘帚。
.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
我們還可以使用 要點一 來填充畅姊,左右兩條豎線和第一條線連接在一起,第二條線和第一條線封閉啥辨。