根據(jù)項(xiàng)目需求惰赋,需要寫一個(gè)正三角形雷達(dá)圖來(lái)展示個(gè)人信息對(duì)比度宠哄,本人最初版本是按照固定的正三角形進(jìn)行繪制的违孝,但是考慮到后續(xù)的擴(kuò)展性,最終決定寫成按數(shù)據(jù)展示的正多邊形集索。剛開始感覺繪制正多邊形時(shí)將問(wèn)題想的很難屿愚,但是逐步思考與實(shí)踐繪制后發(fā)現(xiàn)繪制正多邊形是有一定的固定規(guī)律的。(個(gè)人感覺有些事往往是想著很難但是到實(shí)踐的時(shí)候發(fā)現(xiàn)并沒(méi)那么難)务荆,以下便是我的整體思路和代碼妆距。
將起始點(diǎn)定好,我將起始點(diǎn)的 x 坐標(biāo)定在了當(dāng)前 view 寬度中心處函匕,y 坐標(biāo)自定義娱据。
正多邊形是按照順時(shí)針?lè)较蚶L制的
@interface CYNEdgeView : UIView
//數(shù)據(jù)源
@property (nonatomic, strong)NSArray *infoArray;
@end
@interface CYNEdgeView ()
//中心角的一半(相鄰兩個(gè)點(diǎn)與中心點(diǎn)所成夾角的一半)
@property (nonatomic, assign)CGFloat z;
//半徑(點(diǎn)到中心點(diǎn)的距離)
@property (nonatomic, assign)CGFloat r;
//n 邊形
@property (nonatomic, assign)NSInteger n;
//雷達(dá)圖背景數(shù)組
@property (nonatomic, strong)NSMutableArray *array;
//描繪點(diǎn)雷達(dá)圖數(shù)組
@property (nonatomic, strong)NSMutableArray *edgeArray;
//x(起始點(diǎn)橫坐標(biāo))
@property (nonatomic, assign)CGFloat x;
//y(起始點(diǎn)縱坐標(biāo))
@property (nonatomic, assign)CGFloat y;
//n 邊形高度(通過(guò)計(jì)算可得)
@property (nonatomic, assign)CGFloat h;
@end
對(duì)view進(jìn)行初始化
//起始點(diǎn)坐標(biāo) _x, _y
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
_n = 0;
_z = 0;
_r = 0;
_x = frame.size.width / 2;
_y = 10;
_h = frame.size.height - 20;
_array = [NSMutableArray array];
_minArray = [NSMutableArray array];
_edgeArray = [NSMutableArray array];
}
return self;
}
計(jì)算過(guò)程中發(fā)現(xiàn):
如果邊數(shù)是偶數(shù)的正多邊形,必然有一個(gè)點(diǎn)在中心點(diǎn)和起始點(diǎn)所連成的直線上盅惜,這個(gè)時(shí)候半徑 _r 就是 _h 高度的一半中剩,其余各點(diǎn)以此直線呈現(xiàn)左右對(duì)稱排列忌穿;
如果邊數(shù)是奇數(shù)的正多邊形,必然存在一對(duì)相臨點(diǎn)連成的直線與中心點(diǎn)和起始點(diǎn)所連成的直線成垂直狀態(tài)结啼,這個(gè)時(shí)候半徑 _r 就是 _h / (1 + cos(_z))掠剑,其余各點(diǎn)以此直線呈現(xiàn)左右對(duì)稱排列。
//對(duì)數(shù)據(jù)源進(jìn)行處理
- (void)setInfoArray:(NSArray *)infoArray {
_infoArray = infoArray;
//計(jì)算出多邊形的具體邊數(shù)
_n = [_infoArray.firstObject count];
//計(jì)算出中心角的一半
_z = (360.0 / _n) / 2 * M_PI / 180.0;
//計(jì)算出半徑
_r = _n % 2 == 0 ? _h / 2 : _h / (1 + cos(_z));
//removeAllObjects 是為了配合 drawRect 的 clear 方法郊愧。根據(jù)數(shù)據(jù)進(jìn)行多次加載
[_array removeAllObjects];
[_edgeArray removeAllObjects];
//為左右點(diǎn)的分割點(diǎn)朴译,偶數(shù)多邊形與奇數(shù)多邊形并不一致
NSInteger leftNum = _n % 2 == 0 ? (_n / 2 + 1) : ((_n + 1) / 2);
//描點(diǎn)方式 中 右 左(順時(shí)針?lè)较颍? //計(jì)算雷達(dá)圖背景的點(diǎn)坐標(biāo),分為三層属铁。從外往內(nèi)計(jì)算
for (NSInteger i = 3; i > 0; i--) {
NSMutableArray *backArray = [NSMutableArray array];
//沒(méi)層有 _n 個(gè)元素
for (NSInteger j = 0; j < _n; j++) {
NSDictionary *backDic = @{@"percent" : [NSString stringWithFormat:@"%.2lf", (i / 3.0)]};
[backArray addObject:backDic];
}
[_array addObject:[self calculateXYWithNGraphicsArray:backArray leftNum:leftNum]];
}
//計(jì)算雷達(dá)圖周圍各點(diǎn)眠寿,_infoArray 數(shù)組是按照一定規(guī)則生成的。
//infoArray 數(shù)組為所畫雷達(dá)圖的層數(shù)红选,每一層分為若干個(gè)點(diǎn)澜公,percent 在0~1之間
// _infoArray = @[@[@{@"percent" : @"0.2"}, @{@"percent" : @"0.3"}]];
for (NSInteger i = 0; i < _infoArray.count; i++) {
//edgeArray 是為了承接每層雷達(dá)圖的坐標(biāo)點(diǎn)
[_edgeArray addObject:[self calculateXYWithNGraphicsArray:_infoArray[i] leftNum:leftNum]];
}
//重繪
[self setNeedsDisplay];
}
對(duì)計(jì)算點(diǎn)進(jìn)行封裝
- (NSArray *)calculateXYWithNGraphicsArray:(NSArray *)array leftNum:(NSInteger) leftNum {
NSMutableArray *xyArray = [NSMutableArray array];
for (NSInteger i = 0; i < array.count; i++) {
//獲取所需分?jǐn)?shù)
CGFloat percent = [[array[i] objectForKey:@"percent"] floatValue];
CGFloat edgeX = _x;
CGFloat edgeY = _y;
if (i >= leftNum) {
//左半邊各點(diǎn)
CGFloat z = (360 - i * 360.0 / _n ) / 2.0 * M_PI / 180.0;
CGFloat s = (180.0 - (360 - i * 360.0 / _n)) / 2.0 * M_PI / 180.0;
//計(jì)算當(dāng)前點(diǎn)的坐標(biāo)
edgeX -= _r * percent * sin(z) * 2 * sin(s);
edgeY += _r * (1 - percent) + _r * percent * sin(z) * 2 * cos(s);
}else {
//右半邊各點(diǎn)
CGFloat z = (i * 360.0 / _n) / 2 * M_PI / 180.0;
CGFloat s = (180.0 - i * 360.0 / _n) / 2 * M_PI / 180.0;
//計(jì)算當(dāng)前點(diǎn)的坐標(biāo)
edgeX += _r * percent * sin(z) * 2 * sin(s);
edgeY += _r * (1 - percent) + _r * percent * sin(z) * 2 * cos(s);
}
//坐標(biāo)點(diǎn)處理好后,添加到數(shù)組中
NSDictionary *edgeDic = @{@"x" : [NSString stringWithFormat:@"%.2lf", edgeX], @"y" : [NSString stringWithFormat:@"%.2lf", edgeY]};
[xyArray addObject:edgeDic];
}
return xyArray;
}
對(duì)雷達(dá)圖進(jìn)行繪制
//重寫drawRect方法
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
//判斷數(shù)組不為空時(shí)在進(jìn)行繪制
if (_array.count > 0) {
CGContextRef context = UIGraphicsGetCurrentContext();
//可以用 clear 方法清空繪畫內(nèi)容以便于根據(jù)數(shù)據(jù)進(jìn)行多次加載(**使用 clear 時(shí)需要將當(dāng)前view的背景顏色設(shè)置成 [UIColor clearColor] 顏色喇肋,否則無(wú)論設(shè)置其它任何顏色背景都會(huì)變成黑色**)
//CGContextClearRect(context, rect);
//繪制雷達(dá)圖背景
for (NSInteger i = 0; i < _array.count; i++) {
[self contextStrokeWithColorR:204 G:204 B:204 A:1 textArray:_array[i] context:context width:1];
}
//中心點(diǎn)到各點(diǎn)的連線
CGContextBeginPath(context);
CGContextMoveToPoint(context, _x, _y + _r);
for (NSInteger i = 0; i < _array.count; i++) {
CGContextAddLineToPoint(context, [[[_array.firstObject objectAtIndex:i] objectForKey:@"x"] floatValue], [[[_array.firstObject objectAtIndex:i] objectForKey:@"y"] floatValue]);
CGContextAddLineToPoint(context, _x, _y + _r);
}
CGContextStrokePath(context);
//繪制雷達(dá)圖
for (NSInteger i = 0; i < _edgeArray.count; i++) {
if (i == 0) {
[self contextFillWithColorR:35 G:170 B:165 A:0.5 textArray:_edgeArray[i] context:context];
[self contextStrokeWithColorR:35 G:170 B:165 A:0.9 textArray:_edgeArray[i] context:context width:3];
[self contextRoundFillWithColorR:35 G:170 B:165 A:1 array:_edgeArray[i] context:context];
}else {
[self contextFillWithColorR:173 G:212 B:154 A:0.5 textArray:_edgeArray[i] context:context];
[self contextStrokeWithColorR:173 G:212 B:154 A:0.9 textArray:_edgeArray[i] context:context width:3];
[self contextRoundFillWithColorR:173 G:212 B:154 A:1 array:_edgeArray[i] context:context];
}
}
}
}
對(duì)繪制方法進(jìn)行封裝坟乾,因?yàn)槔走_(dá)圖的背景也調(diào)用該方法所以我將點(diǎn)和線進(jìn)行分開封裝。(封裝方法根據(jù)個(gè)人習(xí)慣隨意)
//線段
- (void)contextStrokeWithColorR:(CGFloat)r G:(CGFloat)g B:(CGFloat)b A:(CGFloat)a textArray:(NSArray *)array context:(CGContextRef)context width:(CGFloat)width {
CGContextBeginPath(context);
CGContextSetLineWidth(context, width);
CGContextSetRGBStrokeColor(context, r / 255.0, g / 255.0, b / 255.0, a);
CGContextMoveToPoint(context, [[array.firstObject objectForKey:@"x"] floatValue], [[array.firstObject objectForKey:@"y"] floatValue]);
for (NSInteger i = 0; i < array.count; i++) {
CGContextAddLineToPoint(context, [[array[i] objectForKey:@"x"] floatValue], [[array[i] objectForKey:@"y"] floatValue]);
}
CGContextAddLineToPoint(context, [[array.firstObject objectForKey:@"x"] floatValue], [[array.firstObject objectForKey:@"y"] floatValue]);
CGContextStrokePath(context);
}
//面
- (void)contextFillWithColorR:(CGFloat)r G:(CGFloat)g B:(CGFloat)b A:(CGFloat)a textArray:(NSArray *)array context:(CGContextRef)context {
CGContextBeginPath(context);
CGContextSetRGBFillColor(context, r / 255.0, g / 255.0, b / 255.0, a);
CGContextMoveToPoint(context, [[array.firstObject objectForKey:@"x"] floatValue], [[array.firstObject objectForKey:@"y"] floatValue]);
for (NSInteger i = 0; i < array.count; i++) {
CGContextAddLineToPoint(context, [[array[i] objectForKey:@"x"] floatValue], [[array[i] objectForKey:@"y"] floatValue]);
}
CGContextAddLineToPoint(context, [[array.firstObject objectForKey:@"x"] floatValue], [[array.firstObject objectForKey:@"y"] floatValue]);
CGContextFillPath(context);
}
//畫圓(本來(lái)用圖片代替圓點(diǎn)的蝶防,但因?yàn)閷蛹?jí)結(jié)構(gòu)問(wèn)題甚侣,出現(xiàn)遮擋問(wèn)題,所以添加了圓點(diǎn)的繪制)
- (void)contextRoundFillWithColorR:(CGFloat)r G:(CGFloat)g B:(CGFloat)b A:(CGFloat)a array:(NSArray *)array context:(CGContextRef)context {
CGFloat radius = 6;
for (NSInteger i = 0; i < array.count; i++) {
CGContextBeginPath(context);
CGContextSetRGBFillColor(context, r / 255.0, g / 255.0, b / 255.0, a);
CGContextAddArc(context, [[array[i] objectForKey:@"x"] floatValue], [[array[i] objectForKey:@"y"] floatValue], radius, 0, 2 * M_PI, 0);
CGContextFillPath(context);
}
}
本人寫的一般间学,只希望能為寫類似雷達(dá)圖的小伙伴提供一點(diǎn)幫助殷费,謝謝!