iOS自定义饼图和柱状图

//
//  SHDrawStatisticPictureView.h
//  testDrawPicture
//
//  Created by vanmr on 2019/9/11.
//  Copyright © 2019 vanmr. All rights reserved.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface SHDrawStatisticPictureView : UIView

///饼图或者柱状图颜色
@property (nonatomic,copy) NSArray <UIColor *>*colors;

/// 初始化
- (instancetype)initWithScoreArray:(NSArray *)scores;

/****************************柱状图***********************/

@property(nonatomic, assign) CGFloat topSpace;
/// 最左边的柱状图距离图表左边框的距离
@property(nonatomic, assign) CGFloat leftSpace;
/// 最右边的柱状图距离图表右边框的距离
@property (nonatomic, assign) CGFloat rightSpace;
/// 坐标系Y 一个刻度的表示的值
@property (nonatomic, assign) NSInteger unitValue;
/// 坐标系Y 总刻度数
@property (nonatomic,assign) CGFloat units;
/// 标尺宽度
@property (nonatomic, assign) CGFloat scaleplateWidth;
/// 图表底部区域的高度
@property (nonatomic,assign) CGFloat chartBottomZoneHeight;
/// 底部区域显示标题集合
@property (nonatomic, copy) NSArray <NSString *>*titles;
/// 底部区域显示的文本颜色
@property (nonatomic, copy) NSArray <UIColor *>*bottomTitleColor;
/// 底部区域默认显示文本颜色
@property (nonatomic, copy) UIColor *defaultTitleColor;
/// 底部字体大小
@property (nonatomic, assign) CGFloat titleFontSize;
/// 坐标系颜色
@property (nonatomic, strong) UIColor *coordinateColor;

/**
 * 画条形图
 * barWidth: 条码宽度
 */
- (void)startDrawBarCharPicture:(CGFloat)barWidth;


/****************************饼图***********************/

/// 中间小圆的半径(饼图)
@property (nonatomic, assign) CGFloat centerRadius;
/// 分割线颜色
@property (nonatomic, strong) UIColor *separateLineColor;
/// 中间圆的背景色
@property (nonatomic, strong) UIColor *centerCircleBackgroundColor;


/**
 * 画饼图
 * radius: 半径
 */
- (void)startDrawPieChartPicture:(CGFloat)radius;

@end

NS_ASSUME_NONNULL_END


//
//  SHDrawStatisticPictureView.m
//  testDrawPicture
//
//  Created by vanmr on 2019/9/11.
//  Copyright © 2019 vanmr. All rights reserved.
//

#import "SHDrawStatisticPictureView.h"


#define defaultFillColor [UIColor purpleColor]

#define defaultBackgroundColor [UIColor whiteColor]

@interface SHDrawStatisticPictureView ()

@property (nonatomic, strong) NSArray *scores;

@property (nonatomic,strong) NSArray *percentStrings;

@property (nonatomic, assign) CGFloat totalScore;
/// 整个饼图的半径
@property (nonatomic, assign) CGFloat pieRadius;

@property (nonatomic, assign) CGPoint barChartOriginPoint;
/// 图表显示的区域
@property (nonatomic, assign) CGSize barChartSize;

@property (nonatomic, assign) CGFloat unitHeight;
/// 当前颜色索引
@property (nonatomic, assign) NSUInteger currentColorIndex;
/// 当前底部标题的索引
@property (nonatomic, assign) NSUInteger currentTitleIndex;
/// 底部一个title单位的宽度
@property (nonatomic, assign) CGFloat bottomTitleUnitWidth;

@end

@implementation SHDrawStatisticPictureView

- (instancetype)initWithScoreArray:(NSArray *)scores{
    
    if (self = [super init]) {
        self.scores = scores;
        self.currentColorIndex = 0;
        self.currentTitleIndex = 0;
        self.backgroundColor = defaultBackgroundColor;
    }
    return self;
}

- (void)handleScoreData:(NSArray *)scores{
    
    if (!scores.count) {
        return;
    }
    __block CGFloat totalScore = 0;
    [self.scores enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        totalScore += [obj floatValue];
    }];
    self.totalScore = totalScore;
    
    NSMutableArray *percnetValueArr = [NSMutableArray array];
    __block int totalScoreExceptLastValue = 0;
    __block int totalScoreValue = 0;
    [self.scores enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        int value = (int)([obj floatValue]/totalScore * 100 + 0.5);
        totalScoreValue += value;
        if (idx < self.scores.count - 1) {
            totalScoreExceptLastValue += value;
        }else{
            value = totalScoreValue - totalScoreExceptLastValue;
        }
        NSString *percentString = percentString = [NSString stringWithFormat:@"%d%%",value];
        [percnetValueArr addObject:percentString];
    }];
    self.percentStrings = percnetValueArr;
}

#pragma mark - 开始画柱状图 ---
- (void)startDrawBarCharPicture:(CGFloat)barWidth{
    
    if (!self.scores.count) {
        return;
    }
    
    if (CGRectEqualToRect(self.frame, CGRectZero)) {
        return;
    }
    
    if (barWidth > self.frame.size.width) {
        return;
    }
    
    [self handleBarCharData];
    // 坐标系
    [self drawCoordinateSystem];
    
    // 柱状图总个数(不包含间距)
    __block CGFloat totalBarCount = 0;
    
    [self.scores enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        
        if ([obj isKindOfClass:[NSArray class]] && obj) {
            NSArray *arr = (NSArray *)obj;
            [arr enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                if (![obj isKindOfClass:[NSArray class]] && obj) {
                    totalBarCount += 1;
                }
            }];
            
        }else{
            totalBarCount += 1;
        }
    }];
    
    self.bottomTitleUnitWidth = (self.barChartSize.width - self.leftSpace - self.rightSpace)/self.scores.count - 10.0;
    
    // 一个数值对应的高度
    CGFloat valueHeight = self.unitHeight / self.unitValue;
    // 坐标原点
    CGFloat originPointX = self.scaleplateWidth;
    CGFloat originPointY = self.frame.size.height - self.chartBottomZoneHeight;
    // 柱与柱之间的间距
    CGFloat barSpace = (self.barChartSize.width - self.leftSpace - self.rightSpace - totalBarCount * barWidth)/(self.scores.count - 1);
    // 每一个柱状图的中心点
    CGFloat pointX = originPointX + self.leftSpace;
    
    
    // 一组柱状图底部的中心点
    CGFloat barGroupCenterX = 0;
    
    for (int i = 0; i<self.scores.count; i++) {
    
            id element = self.scores[i];
            if ([element isKindOfClass:[NSArray class]]) {
                NSArray *elementArr = (NSArray *)element;
                if (!elementArr.count) {
                    continue;
                }
                CGFloat groupWidth = elementArr.count * barWidth; // 一组的宽度
                for (int j = 0; j <elementArr.count; j++) {
                    @autoreleasepool {
                        if (i == 0) {
                            if (j == 0) {
                                pointX += 0.5 *barWidth;
                            }else{
                                pointX += barWidth;
                            }
                        }else{
                            if (j == 0) {
                                pointX += (barWidth + barSpace);
                            }else{
                                pointX += barWidth;
                            }
                        }
                        CGPoint beginPoint = CGPointMake(pointX, originPointY);
                        [self drawOneBarPictureSize:CGSizeMake(barWidth, self.barChartSize.height - [elementArr[j] floatValue] *valueHeight) beginPoint:beginPoint];
                    }
                }
                // 当元素为数组和非数组元素混合时,这里还有问题
                barGroupCenterX = (pointX + 0.5 *barWidth) - 0.5 *groupWidth;
            }else{
                
                @autoreleasepool {
                    if (i == 0) {
                        pointX += 0.5 *barWidth;
                    }else{
                        pointX += (barSpace + barWidth);
                    }
                    CGPoint beginPoint = CGPointMake(pointX, originPointY);
                    [self drawOneBarPictureSize:CGSizeMake(barWidth, self.barChartSize.height - [self.scores[i] floatValue] *valueHeight) beginPoint:beginPoint];
                    barGroupCenterX = pointX;
            }
        }
        
        // 图表底部区域
        [self drawChartBottomZone:CGPointMake(barGroupCenterX, originPointY)];
    }
}

- (void)drawOneBarPictureSize:(CGSize)barSize beginPoint:(CGPoint)beginPoint{
    
    //创建出贝塞尔曲线
    UIBezierPath*circlePath = [UIBezierPath bezierPath];
    [circlePath moveToPoint:beginPoint];
    [circlePath addLineToPoint:CGPointMake(beginPoint.x, barSize.height)];
    [self drawOneBarCharPicture:barSize.width path:circlePath];
}

- (void)handleBarCharData{
    
    if (self.units <= 0) {
        return;
    }
    // 坐标原点
    CGFloat originPointX = self.scaleplateWidth;
    CGFloat originPointY = self.frame.size.height - self.chartBottomZoneHeight;
    self.barChartOriginPoint = CGPointMake(originPointX, originPointY);
    
    // 计算图表区
    CGFloat width = self.frame.size.width - self.scaleplateWidth;
    CGFloat height = self.frame.size.height - self.topSpace - self.chartBottomZoneHeight;
    self.barChartSize = CGSizeMake(width, height);
    
    // 计算每一个标尺的高度
    self.unitHeight =  height / self.units;
}
#pragma mark - 柱状图底部区域绘制 ----
- (void)drawChartBottomZone:(CGPoint)ratePoint{
    
    UIColor *color = [UIColor blackColor];
    if (!self.defaultTitleColor) {
        if (self.bottomTitleColor.count && self.currentTitleIndex < self.bottomTitleColor.count) {
            color = self.bottomTitleColor[self.currentTitleIndex];
        }
    }else{
        color = self.defaultTitleColor;
    }
    
    if (self.titles.count && self.currentTitleIndex < self.titles.count) {
        NSString *title = self.titles[self.currentTitleIndex];
        if (title.length > 0) {
            
            CGFloat fontSize = 14.0;
            if (self.titleFontSize > 0) {
                fontSize = self.titleFontSize;
            }
            
            CATextLayer *textLayer = [[CATextLayer alloc] init];
            textLayer.wrapped = YES;
            textLayer.alignmentMode = kCAAlignmentCenter;
            NSDictionary *attributeDict = @{NSFontAttributeName:[UIFont systemFontOfSize:fontSize],
                                            NSForegroundColorAttributeName:color
                                            };
            NSAttributedString *text = [[NSAttributedString alloc] initWithString:title attributes:attributeDict];
            textLayer.string = text;
            CGFloat textLayerW = self.bottomTitleUnitWidth;
            CGFloat textLayerX = ratePoint.x - textLayerW*0.5;
            CGFloat textLayerY = ratePoint.y;
            CGFloat texlLayerH = self.chartBottomZoneHeight;
            textLayer.frame = CGRectMake(textLayerX, textLayerY, textLayerW, texlLayerH);
            [self.layer addSublayer:textLayer];
            
        }
    }
    
    self.currentTitleIndex++;
}
#pragma mark - 画坐标系 ---
- (void)drawCoordinateSystem{
    
    if (self.units <= 0) {
        return;
    }
    
    UIColor *coordinateColor = [UIColor redColor];
    if (self.coordinateColor) {
        coordinateColor = self.coordinateColor;
    }
    CAShapeLayer *coordinateLayer = [[CAShapeLayer alloc] init];
    coordinateLayer.strokeColor = coordinateColor.CGColor;
    coordinateLayer.lineWidth = 1.0;
    
    UIBezierPath *path = [UIBezierPath bezierPath];
    for (int i = 0; i<self.units + 1; i++) {
        
        @autoreleasepool {
            
            [path moveToPoint:CGPointMake(self.barChartOriginPoint.x - 10.0, self.barChartOriginPoint.y - i *self.unitHeight)];
            [path addLineToPoint:CGPointMake(self.barChartOriginPoint.x - 10.0 + self.barChartSize.width, self.barChartOriginPoint.y - i *self.unitHeight)];
            
            CATextLayer *scaleplateTextLayer = [[CATextLayer alloc] init];
            NSDictionary *attributeDict = @{NSFontAttributeName:[UIFont systemFontOfSize:14.0],
                                            NSForegroundColorAttributeName:[UIColor blackColor]
                                            };
            NSAttributedString *text = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%ld",self.unitValue * i] attributes:attributeDict];
            scaleplateTextLayer.string = text;
            scaleplateTextLayer.frame = CGRectMake(0, self.barChartOriginPoint.y - i*self.unitHeight - 14.0, 20.0, 14.0);
            [self.layer addSublayer:scaleplateTextLayer];
        }
    }
    
    [path moveToPoint:CGPointMake(self.barChartOriginPoint.x, self.barChartOriginPoint.y)];
    [path addLineToPoint:CGPointMake(self.barChartOriginPoint.x, self.topSpace)];
    
    coordinateLayer.path = path.CGPath;
    [self.layer addSublayer:coordinateLayer];
    
}

- (CAShapeLayer *)drawOneBarCharPicture:(CGFloat)barWidth path:(UIBezierPath *)path{
    
    UIColor *strokeColor = defaultFillColor;
    if (self.colors.count && self.currentColorIndex < self.colors.count) {
        UIColor *color = self.colors[self.currentColorIndex];
        if (color) {
            strokeColor = color;
        }
    }
    
    CAShapeLayer *shapeLayer= [CAShapeLayer layer];
    shapeLayer.lineWidth=barWidth;
    shapeLayer.strokeColor= strokeColor.CGColor;
    shapeLayer.path= path.CGPath;
    [self.layer addSublayer:shapeLayer];
    self.currentColorIndex++;
    return shapeLayer;
}
#pragma mark - 开始画饼图 ---
- (void)startDrawPieChartPicture:(CGFloat)radius{
    
    if (!self.scores.count) {
        return;
    }
    
    if (CGRectEqualToRect(self.frame, CGRectZero)) {
        return;
    }
    
    if (radius > self.frame.size.width || radius > self.frame.size.height) {
        return;
    }
    
    [self handleScoreData:self.scores];
    
    self.pieRadius = radius;
    
    CGFloat startA = 0;
    CGFloat endA = 0;
    
    for (int i = 0; i < self.scores.count; i++) {
        @autoreleasepool {
            CGFloat score = [self.scores[i] floatValue];
            if (score > 0) {
                CGFloat angle = M_PI * 2 * score/self.totalScore;
                endA += angle;
                [self drawPieChartPartPicture:radius startAngle:startA endAngle:endA  percentage:self.percentStrings[i]];
                startA = endA;
            }
        }
    }
    
    [self addCenterCircleToLayer];
}
#pragma mark - 画中间圆 ----
- (void)addCenterCircleToLayer{
    
    CAShapeLayer *centerCircleLayer = [[CAShapeLayer alloc] init];
    centerCircleLayer.fillColor = defaultBackgroundColor.CGColor;
    if (self.centerCircleBackgroundColor) {
        centerCircleLayer.fillColor = self.centerCircleBackgroundColor.CGColor;
    }
    
    CGFloat centerRadius = self.pieRadius * 0.4;
    if (self.centerRadius > 0) {
        centerRadius = self.centerRadius;
    }
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.frame.size.width * 0.5, self.frame.size.height * 0.5) radius:centerRadius startAngle:0 endAngle:M_PI * 2 clockwise:YES];
    centerCircleLayer.path = path.CGPath;
    [self.layer addSublayer:centerCircleLayer];
}

- (void)drawPieChartPartPicture:(CGFloat)radius startAngle:(CGFloat)startA endAngle:(CGFloat)endA percentage:(NSString *)percentage{
    
    UIColor *fillColor = defaultFillColor;
    if (self.colors.count && self.currentColorIndex < self.colors.count) {
        UIColor *color = self.colors[self.currentColorIndex];
        if (color) {
            fillColor = color;
        }
    }
    
    CAShapeLayer *shapeLayer = [[CAShapeLayer alloc] init];
    shapeLayer.strokeColor = defaultBackgroundColor.CGColor;
    if (self.separateLineColor) {
        shapeLayer.strokeColor = self.separateLineColor.CGColor;
    }
    shapeLayer.fillColor = fillColor.CGColor;
    shapeLayer.lineWidth = 5.0;
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path addArcWithCenter:CGPointMake(self.frame.size.width * 0.5, self.frame.size.height * 0.5) radius:radius startAngle:startA endAngle:endA clockwise:YES];
    [path addLineToPoint:CGPointMake(self.frame.size.width * 0.5, self.frame.size.height * 0.5)];
    [path closePath];
    shapeLayer.path = path.CGPath;
    [self.layer addSublayer:shapeLayer];
    
    CAShapeLayer *lineLayer = [[CAShapeLayer alloc] init];
    lineLayer.strokeColor = fillColor.CGColor;
    lineLayer.fillColor = [UIColor clearColor].CGColor;
    lineLayer.lineWidth = 2.0;
    UIBezierPath *linePath = [UIBezierPath bezierPath];
    CGFloat resultA = (endA - startA)*0.5 + startA;
    
    //     扇形中心位置的坐标
    CGFloat anglePointX = radius * cosf(resultA) + self.frame.size.width * 0.5;
    CGFloat anglePointY = radius * sinf(resultA) + self.frame.size.height * 0.5;
    [linePath moveToPoint:CGPointMake(anglePointX, anglePointY)];
  
    NSDictionary *attributeDict = @{ NSFontAttributeName:[UIFont systemFontOfSize:16.0],
                                     NSForegroundColorAttributeName:[UIColor blackColor]
                                     };
    NSAttributedString *percentageAttributeString = [[NSAttributedString alloc] initWithString:percentage attributes:attributeDict];
    CATextLayer *textlayer = [[CATextLayer alloc] init];
    textlayer.string = percentageAttributeString;
    
    CGFloat slopeLineAngle = 1/3.0 * M_PI;
    CGFloat addX = 20.0 * cosf(slopeLineAngle);
    CGFloat addY = 20.0 * sinf(slopeLineAngle);
    CGFloat horizontalLineLength = 30.0;
    
    CGPoint onePoint = CGPointZero;
    CGPoint twoPoint = CGPointZero;
    if (resultA < M_PI_2 && resultA > 0) { // 左下区间
        onePoint = CGPointMake(addX + anglePointX, addY + anglePointY);
        twoPoint = CGPointMake(onePoint.x + horizontalLineLength, onePoint.y);
        textlayer.frame = CGRectMake(twoPoint.x + 5.0, twoPoint.y - 15.0, 60, 44);
    }
    
    if (resultA < M_PI && resultA >= M_PI_2) { // 右下区间
        onePoint = CGPointMake(anglePointX - addX, addY + anglePointY);
        twoPoint = CGPointMake(onePoint.x - horizontalLineLength, onePoint.y);
        textlayer.frame = CGRectMake(twoPoint.x - 38.0, twoPoint.y - 15.0, 60, 44);
    }
    
    if (resultA < M_PI * 1.5 && resultA >= M_PI) { // 右上区间
        onePoint = CGPointMake(anglePointX - horizontalLineLength, anglePointY);
        twoPoint = CGPointMake(onePoint.x - addX, onePoint.y - addY);
        textlayer.frame = CGRectMake(twoPoint.x - 10.0, twoPoint.y - 20.0, 60, 44);
    }
    
    if (resultA < M_PI * 2.0 && resultA >= M_PI * 1.5) { // 左上区间
        onePoint = CGPointMake(anglePointX + horizontalLineLength, anglePointY);
        twoPoint = CGPointMake(onePoint.x + addX, onePoint.y - addY);
        textlayer.frame = CGRectMake(twoPoint.x - 10.0, twoPoint.y - 20.0, 60, 44);
    }
     [self.layer addSublayer:textlayer];
    
    [linePath addLineToPoint:onePoint];
    [linePath addLineToPoint:twoPoint];
    lineLayer.path = linePath.CGPath;
    [self.layer addSublayer:lineLayer];
    
    self.currentColorIndex++;
}

@end


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容