iOS drawRect 雷達(dá)圖的繪制

根據(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)幫助殷费,謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末低葫,一起剝皮案震驚了整個(gè)濱河市详羡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嘿悬,老刑警劉巖实柠,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異善涨,居然都是意外死亡窒盐,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門钢拧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蟹漓,“玉大人,你說(shuō)我怎么就攤上這事源内∑狭#” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)嗽交。 經(jīng)常有香客問(wèn)我伯铣,道長(zhǎng),這世上最難降的妖魔是什么轮纫? 我笑而不...
    開封第一講書人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮焚鲜,結(jié)果婚禮上掌唾,老公的妹妹穿的比我還像新娘。我一直安慰自己忿磅,他們只是感情好糯彬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著葱她,像睡著了一般撩扒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上吨些,一...
    開封第一講書人閱讀 51,443評(píng)論 1 302
  • 那天搓谆,我揣著相機(jī)與錄音,去河邊找鬼豪墅。 笑死泉手,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的偶器。 我是一名探鬼主播斩萌,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼屏轰!你這毒婦竟也來(lái)了颊郎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤霎苗,失蹤者是張志新(化名)和其女友劉穎姆吭,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叨粘,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡猾编,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了升敲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片答倡。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖驴党,靈堂內(nèi)的尸體忽然破棺而出瘪撇,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布倔既,位于F島的核電站恕曲,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏渤涌。R本人自食惡果不足惜佩谣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望实蓬。 院中可真熱鬧茸俭,春花似錦、人聲如沸安皱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)酌伊。三九已至腾窝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間居砖,已是汗流浹背虹脯。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留奏候,地道東北人归形。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像鼻由,于是被迫代替她去往敵國(guó)和親暇榴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容