美文网首页
iOS自定义饼图和柱状图

iOS自定义饼图和柱状图

作者: 扶兮摇兮 | 来源:发表于2020-04-16 15:30 被阅读0次
//
//  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


相关文章

网友评论

      本文标题:iOS自定义饼图和柱状图

      本文链接:https://www.haomeiwen.com/subject/wborvhtx.html