UIBezierPath繪制柱狀圖、折線圖和餅狀圖

最近用UIBezierPath繪制了一些圖形喻杈,像柱狀圖扫俺、折線圖和餅狀圖之類的圖形苍苞。先上效果圖:

基本原理:
  • 利用UIBezierPath能夠創(chuàng)建基于矢量路徑的特性來繪制圖形的路徑,然后將UIBezierPathCAShapeLayer建立關(guān)系狼纬,讓后者在前者提供的路徑中進(jìn)行渲染羹呵,最后生成我們所需的各種圖形。而且可以給CAShapeLayer添加動畫特效疗琉。

一冈欢、柱狀圖

實現(xiàn)思路:
  • 對數(shù)據(jù)源進(jìn)行分析,獲取數(shù)據(jù)源的最大值盈简,以及最大值和柱狀圖高度的比例涛癌,用于其他數(shù)據(jù)等比例顯示。數(shù)據(jù)源為:
- (NSArray *)topArray {
    return @[@"342",@"900",@"505",@"1780",@"1450",@"30",@"1000”];
}
- (NSArray *)bottomArray {
    return @[@"1月",@"2月",@"3月",@"4月",@"5月",@"6月",@"7月”];
}

獲取數(shù)據(jù)源最大值以及比例:

//獲取數(shù)據(jù)最大值
float max = [[self.topArray valueForKeyPath:@"@max.intValue"] floatValue];
//獲取比例大小
float scale = (K_HEIGHT-K_LABEL_HEIGHT*2)/max;
  • 然后利用UIBezierPath繪制單個柱狀的起點和終點的連線:
//柱狀圖
UIBezierPath * bePath = [UIBezierPath bezierPath];
//起點
[bePath moveToPoint:CGPointMake(K_WIDTH/(count*2)+K_WIDTH/count*i, K_HEIGHT-K_LABEL_HEIGHT)];
//終點
[bePath addLineToPoint:CGPointMake(K_WIDTH/(count*2)+K_WIDTH/count*i, K_HEIGHT-K_LABEL_HEIGHT-[self.topArray[i] floatValue]*scale)];
[bePath stroke];
  • 創(chuàng)建CAShapeLayer(其屬性作用在下面分類中有說明)送火,并與UIBezierPath建立關(guān)系拳话,即設(shè)置path屬性:
//添加CAShapeLayer
_shaLayer = [CAShapeLayer layerWithFillColor:[UIColor clearColor].CGColor strokeColor:[UIColor greenColor].CGColor strokeStart:0.0f strokeEnd:1.0f zPosition:1 lineWidth:30.0f path:bePath.CGPath];
 [self.layer addSublayer:_shaLayer];

CAShapeLayer的分類方法為:

@implementation CAShapeLayer (Category)

/** CAShapeLayer
 * @param fillColor   填充顏色
 * @param strokeColor 填充路徑的描邊輪廓的顏色
 * @param strokeStart 表示路徑的起點,在[0,1]的范圍內(nèi)
 * @param strokeEnd   表示路徑的終點,在[0,1]的范圍內(nèi)
 * @param zPosition   表示在superlayer中的位置
 * @param lineWidth   填充路徑的線寬
 * @param path        表示要呈現(xiàn)形狀的路徑
 */
+ (CAShapeLayer *)layerWithFillColor:(CGColorRef)fillColor strokeColor:(CGColorRef)strokeColor strokeStart:(CGFloat)strokeStart strokeEnd:(CGFloat)strokeEnd zPosition:(CGFloat)zPosition lineWidth:(CGFloat)lineWidth path:(CGPathRef)path {
    CAShapeLayer * layer = [CAShapeLayer layer];
    layer.fillColor = fillColor;
    layer.strokeColor = strokeColor;
    layer.strokeStart = strokeStart;
    layer.strokeEnd = strokeEnd;
    layer.zPosition = zPosition;
    layer.lineWidth = lineWidth;
    layer.path  = path;
    return layer;
}
@end
  • 為創(chuàng)建的CAShapeLayer添加動畫特效:
//動畫
- (void)startStroke {
    [_shaLayer addAnimation:self.pathAnimation forKey:@"strokeEndAnimation”];
}

動畫方法為:

//動畫
- (CABasicAnimation *)pathAnimation {
    CABasicAnimation * pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd”];
    pathAnimation.duration = 2.0f;
    pathAnimation.fromValue = @0.0f;//動畫開始位置
    pathAnimation.toValue = @1.0f;//動畫停止位置
    pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];//添加動畫樣式
    return pathAnimation;
}
  • 為每個柱狀創(chuàng)建上下數(shù)據(jù)展示label
//上label
[self addSubview:[UILabel labelWithFrame:CGRectMake(K_WIDTH/count*i, K_HEIGHT-60-[self.topArray[i] floatValue]*scale, K_WIDTH/count, K_LABEL_HEIGHT) text:self.topArray[i] textColor:[UIColor redColor] textAlignment:NSTextAlignmentCenter font:15]];
//下label
[self addSubview:[UILabel labelWithFrame:CGRectMake(K_WIDTH/count*i, K_HEIGHT-K_LABEL_HEIGHT, K_WIDTH/count, K_LABEL_HEIGHT) text:self.bottomArray[i] textColor:[UIColor blackColor] textAlignment:NSTextAlignmentCenter font:13]];
  • 最后按數(shù)據(jù)源個數(shù)對以上控件進(jìn)行循環(huán)創(chuàng)建。
最終實現(xiàn)效果為:
柱狀圖.gif

二种吸、折線圖

折線圖的實現(xiàn)方法和柱狀圖的實現(xiàn)方法類似弃衍,主要在于折線圖需要創(chuàng)建一個坐標(biāo)體系,并對數(shù)據(jù)源中每個坐標(biāo)點進(jìn)行連線繪制坚俗。直接上代碼(數(shù)據(jù)源和柱狀圖一樣):

#import “BrokenView.h”
#import "UILabel+Category.h”

#define K_WIDTH          CGRectGetWidth(self.frame)
#define K_HEIGHT         CGRectGetHeight(self.frame)
#define K_LABEL_HEIGHT   30
#define K_ACROSS_NUM     6 //橫線默認(rèn)條數(shù)
@implementation BrokenView {
    CAShapeLayer * _shaLayer;
}

- (instancetype)initWithFrame:(CGRect)frame topArray:(NSArray *)topArray bottoArray:(NSArray *)bottomArray {
    if (self = [super initWithFrame:frame]) {
        self.backgroundColor = [UIColor whiteColor];
        self.topArray = topArray;
        self.bottomArray = bottomArray;
    }
    return self;
}

//動畫
- (void)startStroke {
    [_shaLayer addAnimation:self.pathAnimation forKey:@"strokeEndAnimation”];
}

- (void)drawRect:(CGRect)rect {
    NSUInteger count = self.topArray.count;
    if (count<1) return;
    //獲取數(shù)據(jù)最大值
    float max = [[self.topArray valueForKeyPath:@"@max.intValue"] floatValue];//獲取比例大小
    float scale = (K_HEIGHT-K_LABEL_HEIGHT*2)/max;//上下兩個label高度和
    
    //繪制坐標(biāo)系
    for (int i=0; i<K_ACROSS_NUM; i++) {
        //橫線
        UIBezierPath * across = [UIBezierPath bezierPath];
        [across moveToPoint:CGPointMake(0, (K_HEIGHT-K_LABEL_HEIGHT)/K_ACROSS_NUM*(i+1))];
        [across addLineToPoint:CGPointMake(K_WIDTH, (K_HEIGHT-K_LABEL_HEIGHT)/K_ACROSS_NUM*(i+1))];
        [[UIColor greenColor] set];
        [across stroke];
    }
    
    for (int j=0; j<count; j++) {
        //豎線
        UIBezierPath * vertical = [UIBezierPath bezierPath];
        [vertical moveToPoint:CGPointMake(K_WIDTH/(count*2)+K_WIDTH/count*j, K_HEIGHT-K_LABEL_HEIGHT)];
        [vertical addLineToPoint:CGPointMake(K_WIDTH/(count*2)+K_WIDTH/count*j, 0)];
        [[UIColor greenColor] set];
        [vertical stroke];
        
        //繪制各坐標(biāo)點
        UIBezierPath * point = [UIBezierPath bezierPathWithArcCenter:CGPointMake(K_WIDTH/(count*2)+K_WIDTH/count*j, K_HEIGHT-K_LABEL_HEIGHT-[self.topArray[j] floatValue]*scale) radius:5.0f startAngle:-M_PI endAngle:M_PI*3 clockwise:YES];
        CAShapeLayer * pointLayer = [CAShapeLayer layerWithFillColor:[UIColor blueColor].CGColor strokeColor:[UIColor clearColor].CGColor strokeStart:0.0f strokeEnd:1.0f zPosition:0 lineWidth:0.0f path:point.CGPath];
        [self.layer addSublayer:pointLayer];
        
        //上label
        [self addSubview:[UILabel labelWithFrame:CGRectMake(K_WIDTH/count*j, K_HEIGHT-60-[self.topArray[j] floatValue]*scale, K_WIDTH/count, K_LABEL_HEIGHT) text:self.topArray[j] textColor:[UIColor redColor] textAlignment:NSTextAlignmentCenter font:15]];
        //下label
        [self addSubview:[UILabel labelWithFrame:CGRectMake(K_WIDTH/count*j, K_HEIGHT-K_LABEL_HEIGHT, K_WIDTH/count, K_LABEL_HEIGHT) text:self.bottomArray[j] textColor:[UIColor blackColor] textAlignment:NSTextAlignmentCenter font:13]];
    }
    //繪制折線
    UIBezierPath * broPath = [UIBezierPath bezierPath];
    [broPath moveToPoint:CGPointMake(K_WIDTH/(count*2), K_HEIGHT-K_LABEL_HEIGHT-[self.topArray[0] floatValue]*scale)];
    for (int j=1; j<count; j++) {
        [broPath addLineToPoint:CGPointMake(K_WIDTH/(count*2)+K_WIDTH/count*j, K_HEIGHT-K_LABEL_HEIGHT-[self.topArray[j] floatValue]*scale)];
    }
    [broPath stroke];
    _shaLayer = [CAShapeLayer layer];
    _shaLayer.lineWidth = 2.0f;
    _shaLayer.fillColor = [UIColor clearColor].CGColor;
    _shaLayer.strokeColor = [UIColor blueColor].CGColor;
    _shaLayer.path = broPath.CGPath;
    [self.layer addSublayer:_shaLayer];
    
    [self startStroke];
}
@end
最終實現(xiàn)效果為:
折線圖.gif

三镜盯、餅狀圖

實現(xiàn)思路:
  • 首先設(shè)置餅狀圖中心點以及半徑:
//設(shè)置餅狀圖中心點
CGFloat centerX = K_WIDTH * 0.5f;
CGFloat centerY = K_HEIGHT * 0.5f;
CGPoint centerPoint = CGPointMake(centerX, centerY);
//設(shè)置半徑
CGFloat radius = MIN(centerX, centerY) * 0.5f;//MIN(A,B)為獲取兩者最小值
  • 設(shè)置數(shù)據(jù)源:
- (NSArray *)pieArray {
    return @[@"70",@"60",@"100",@"50",@"80"];
}
- (NSArray *)colorArray {
    return @[[UIColor redColor],[UIColor purpleColor],[UIColor blueColor],[UIColor orangeColor],[UIColor blackColor]];
}
  • 獲取數(shù)據(jù)源數(shù)據(jù)總和岸裙,用于后面扇形劃分比例:
//獲取展示數(shù)據(jù)總和
CGFloat nums = 0.0f;
for (int i=0; i<self.dataArray.count; i++) {
    nums += [self.dataArray[i] floatValue];
}
  • 創(chuàng)建一個背景圓,用于后期添加動畫特效:
    //繪制背景圓的路徑
    UIBezierPath * backPath = [UIBezierPath bezierPathWithArcCenter:centerPoint
                                                             radius:radius
                                                         startAngle:-M_PI_2
                                                           endAngle:M_PI_2*3
                                                          clockwise:YES];
    _backLayer = [CAShapeLayer layerWithFillColor:[UIColor clearColor].CGColor
                                      strokeColor:[UIColor greenColor].CGColor
                                      strokeStart:0.0f
                                        strokeEnd:1.0f
                                        zPosition:1
                                        lineWidth:radius * 2.0f
                                             path:backPath.CGPath];

UIBezierPath繪制圓形方法+ (instancetype)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;中參數(shù)如下:center是弧線中心點的坐標(biāo)速缆; radius是弧線所在圓的半徑降允; startAngle是弧線開始的角度值; endAngle是弧線結(jié)束的角度值艺糜; clockwise表示是否順時針畫弧線剧董。

  • UIBezierPath繪制各個扇形的路徑,和背景圓路徑一樣:
//繪制各個扇形的路徑
    UIBezierPath * subPath = [UIBezierPath bezierPathWithArcCenter:centerPoint
                                                            radius:radius
                                                        startAngle:-M_PI_2
                                                          endAngle:M_PI_2*3
                                                         clockwise:YES];
  • 分別獲取餅狀圖中每個扇形的起點strokeStart和終點strokeEnd破停,并按數(shù)據(jù)源的個數(shù)循環(huán)創(chuàng)建每個扇形的CAShapeLayer翅楼,并與subPath相關(guān)聯(lián),并以此獲取扇形的形狀真慢。
 //設(shè)置各個扇形開始和結(jié)束位置
    CGFloat start = 0.0f;
    CGFloat end = 0.0f;
    for (int i=0; i<self.dataArray.count; i++) {
        end = [self.dataArray[i] floatValue]/nums + start;
        CGColorRef strokeColor = (!self.colorArray ||  self.colorArray.count == 0 || i>self.colorArray.count-1) ? [UIColor purpleColor].CGColor : ((UIColor *)self.colorArray[i]).CGColor;
        CAShapeLayer * subLayer = [CAShapeLayer layerWithFillColor:[UIColor clearColor].CGColor
                                                       strokeColor:strokeColor
                                                       strokeStart:start
                                                         strokeEnd:end
                                                         zPosition:2
                                                         lineWidth:radius * 2.0f
                                                              path:subPath.CGPath];
        [self.layer addSublayer:subLayer];
        
        //百分比label
        CGFloat angle = M_PI * (start + end);//扇形角度
        CGFloat labelCenterX = centerX * 0.5f * sinf(angle) + centerX;
        CGFloat labelCenterY = -centerX * 0.5f * cosf(angle) + centerY;
        UILabel * label = [UILabel labelWithFrame:CGRectMake(0, 0, radius * 0.8f, radius * 0.3f) text:[NSString stringWithFormat:@"%@  %ld%%",self.dataArray[i],(NSInteger)((end-start+0.005)*100)] textColor:[UIColor redColor] textAlignment:NSTextAlignmentCenter font:15];
        label.center = CGPointMake(labelCenterX, labelCenterY);
        label.backgroundColor = [UIColor whiteColor];
        label.layer.zPosition = 3;
        [self addSubview:label];
        
        start = end;
    }

其中以每個扇形的中軸線的中點為中心點來創(chuàng)建的label用于顯示扇形的比例毅臊。中心點坐標(biāo)是利用三角形的正弦函數(shù)和余弦函數(shù)來確定的。

  • 最后為背景圓的Layer添加動畫:
//動畫
- (void)startStroke {
    [_backLayer addAnimation:self.pathAnimation forKey:@"circleAnimation”];
}
最終實現(xiàn)效果為:

餅狀圖.gif

Demo地址:UIBezierPath繪制柱狀圖黑界、折線圖和餅狀圖

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末管嬉,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子朗鸠,更是在濱河造成了極大的恐慌蚯撩,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件童社,死亡現(xiàn)場離奇詭異求厕,居然都是意外死亡,警方通過查閱死者的電腦和手機扰楼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進(jìn)店門呀癣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人弦赖,你說我怎么就攤上這事项栏。” “怎么了蹬竖?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵沼沈,是天一觀的道長。 經(jīng)常有香客問我币厕,道長列另,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任旦装,我火速辦了婚禮页衙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己店乐,他們只是感情好艰躺,可當(dāng)我...
    茶點故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著眨八,像睡著了一般腺兴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上廉侧,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天页响,我揣著相機與錄音,去河邊找鬼伏穆。 笑死拘泞,一個胖子當(dāng)著我的面吹牛纷纫,可吹牛的內(nèi)容都是我干的枕扫。 我是一名探鬼主播,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼辱魁,長吁一口氣:“原來是場噩夢啊……” “哼烟瞧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起染簇,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤参滴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后锻弓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體砾赔,經(jīng)...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年青灼,在試婚紗的時候發(fā)現(xiàn)自己被綠了暴心。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,664評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡杂拨,死狀恐怖专普,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情弹沽,我是刑警寧澤檀夹,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站策橘,受9級特大地震影響炸渡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜丽已,卻給世界環(huán)境...
    茶點故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一蚌堵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧促脉,春花似錦辰斋、人聲如沸策州。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽够挂。三九已至,卻和暖如春藕夫,著一層夾襖步出監(jiān)牢的瞬間孽糖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工毅贮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留办悟,地道東北人。 一個月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓滩褥,卻偏偏與公主長得像病蛉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子瑰煎,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,554評論 2 349

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