iOS Objective-C 自定义饼状图
前些天项目中使用了饼状图,现在开发完做下记录。
//
// PieChartView.h
#import <UIKit/UIKit.h>
@protocol PieChartViewDelegate
@required
-(NSInteger)getChartRows;
-(NSString*)getChartName:(NSInteger)row;
-(CGFloat)getChartRate:(NSInteger)row;
-(void)didSelectChartView:(NSInteger)row;
@end
@interface PieChartView : UIView
@property(nonatomic,weak)id<PieChartViewDelegate> delegate;
@end
//
// PieChartView.m
// NbanClient
//
#import "PieChartView.h"
static NSArray *chartColors;
@interface PieChartView()<CAAnimationDelegate>
{
NSInteger _currentIndex;
BOOL _isShowAnimate;//判断自身是否处于外部
UIView *_circleView;//中心圆形
}
@property(nonatomic,copy)NSArray *chartColors;
@property(nonatomic,strong)NSMutableArray *chartRanColors;
@property(nonatomic,strong)NSMutableArray *chartPaths;
@property(nonatomic,strong)NSMutableArray *chartLinePaths;
@property(nonatomic,strong)NSMutableArray<UILabel*> *chartLabels;
@property(nonatomic,strong)NSMutableArray<NSNumber*> *labelWidths;
@property(nonatomic,strong)CAShapeLayer* animateBackLayer;
@end
@implementation PieChartView
- (instancetype)init
{
self = [super init];
if (self) {
self.backgroundColor = UIColorFromRGB(0xf2f2f2);
UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(taphandle:)];
[self addGestureRecognizer:gesture];
_chartPaths = [[NSMutableArray alloc]init];
_chartLinePaths = [[NSMutableArray alloc]init];
_chartRanColors = [[NSMutableArray alloc]init];
_chartLabels = [[NSMutableArray alloc]init];
_labelWidths = [[NSMutableArray alloc]init];
}
return self;
}
- (void)drawRect:(CGRect)rect {
[_chartLinePaths removeAllObjects];
[_chartPaths removeAllObjects];
[_chartRanColors removeAllObjects];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColorFromRGB(0xffffff) CGColor]);
CGContextSetFillColorWithColor(context, [UIColorFromRGB(0xffffff) CGColor]);
CGContextAddRect(context, CGRectMake(AdapterW(16), 0, screen_width-AdapterW(32), rect.size.height));
CGContextDrawPath(context, kCGPathFillStroke);
if (_delegate) {
NSInteger count = [_delegate getChartRows];
NSMutableArray *currentChartColors = [[NSMutableArray alloc]initWithArray:self.chartColors];
CGFloat allRate = 0;
for (int i = 0; i<count; i++) {
if ([currentChartColors count] == 0) {
currentChartColors = [[NSMutableArray alloc]initWithArray:self.chartColors];
}
UIBezierPath *path = [UIBezierPath bezierPath];
NSInteger ran = random()%[currentChartColors count];
[_chartRanColors addObject:currentChartColors[ran]];
CGContextSetStrokeColorWithColor(context, [[UIColor whiteColor] CGColor]);
CGContextSetFillColorWithColor(context, [currentChartColors[ran] CGColor]);
CGFloat rate = [_delegate getChartRate:i];
[path moveToPoint:self.center];
[path addArcWithCenter:self.center radius:75 startAngle:M_PI*2*allRate endAngle:M_PI*2*(rate+allRate) clockwise:YES];
// CGContextMoveToPoint(context, rect.size.width/2, rect.size.height/2);
//CGContextAddArc(context, rect.size.width/2, rect.size.height/2, 75, M_PI*2*allRate, M_PI*2*(rate+allRate), NO);
[path closePath];
CGContextAddPath(context, [path CGPath]);
CGContextDrawPath(context, kCGPathFillStroke);
[_chartPaths addObject:path];
UIBezierPath *linePath = [UIBezierPath bezierPath];
CGContextSetStrokeColorWithColor(context, [currentChartColors[ran] CGColor]);
CGPoint midPoint = [self getArcMidPoint:rate/2+allRate];
CGPoint endPoint = [self getArcEndMidPoint:rate/2+allRate district:[self isDistrctLine:rate/2+allRate] endRadius:20];
[linePath moveToPoint:CGPointMake(midPoint.x, midPoint.y)];
//CGContextMoveToPoint(context, midPoint.x, midPoint.y);
if([self isDistrctLine:rate/2+allRate]){
[linePath addLineToPoint:endPoint];
// CGContextAddLineToPoint(context, endPoint.x, endPoint.y);
}
else {
CGPoint controlPoint = [self getArcControlMidPoint:rate/2+allRate];
[linePath addQuadCurveToPoint:endPoint controlPoint:controlPoint];
//CGContextAddQuadCurveToPoint(context, controlPoint.x, controlPoint.y, endPoint.x, endPoint.y);
}
CGContextAddPath(context, [linePath CGPath]);
CGContextStrokePath(context);
[_chartLinePaths addObject:linePath];
CGFloat rateWidth = [[APPManager defaultManager] getTextWidth:[NSString stringWithFormat:@"%.2f%%",[_delegate getChartRate:i]*100] setHeight:20 setFont:[UIFont systemFontOfSize:AdapterF(9)]];
NSString *name = [NSString stringWithFormat:@"%@\n%.2f%%",[_delegate getChartName:i],[_delegate getChartRate:i]*100];
CGFloat labelWidth = [[APPManager defaultManager] getTextWidth:name setHeight:20 setFont:[UIFont systemFontOfSize:AdapterF(9)]];
if (labelWidth<rateWidth) {
labelWidth = rateWidth;
}
if (midPoint.x>endPoint.x) {
if (endPoint.x-8<labelWidth) {
labelWidth = endPoint.x-8;
}
}
else {
if (self.frame.size.width-endPoint.x-8<labelWidth) {
labelWidth = self.frame.size.width-endPoint.x-8;
}
}
[_labelWidths addObject:[NSNumber numberWithFloat:labelWidth]];
UILabel*label = [[UILabel alloc]init];
//label.backgroundColor = UIColorFromRGB(0xff0000);
label.textColor = UIColorFromRGB(0x999999);
label.font = [UIFont systemFontOfSize:AdapterF(8)];
label.text = name;
label.textAlignment = NSTextAlignmentCenter;
label.numberOfLines = 0;
[self addSubview:label];
[label mas_makeConstraints:^(MASConstraintMaker *make) {
if (midPoint.x>endPoint.x) {
make.right.mas_equalTo(-(self.frame.size.width-endPoint.x)-8);
//make.left.mas_equalTo(0);
}
else {
make.left.mas_equalTo(endPoint.x+8);
// make.right.mas_equalTo(0);
}
make.top.mas_equalTo(endPoint.y-10);
make.height.mas_equalTo(25);
make.width.mas_equalTo(labelWidth);
}];
[_chartLabels addObject:label];
allRate = allRate + rate;
[currentChartColors removeObjectAtIndex:ran];
}
UIView *circleView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 13, 13)];
circleView.layer.cornerRadius = 6.5;
circleView.backgroundColor = [UIColor whiteColor];
circleView.center = self.center;
[self addSubview:circleView];
[circleView bringSubviewToFront:circleView];
// CGContextSetStrokeColorWithColor(context, [[UIColor whiteColor] CGColor]);
// CGContextSetFillColorWithColor(context, [[UIColor whiteColor] CGColor]);
// CGContextAddArc(context, rect.size.width/2, rect.size.height/2, 6.5, 0, M_PI*2, NO);
// CGContextDrawPath(context, kCGPathFillStroke);
}
}
- (NSArray*)chartColors{
if (!_chartColors) {
_chartColors = [[NSArray alloc]initWithObjects:UIColorFromRGB(0xFFEF6B),UIColorFromRGB(0xFF814A),UIColorFromRGB(0x56CA77),UIColorFromRGB(0xFFA6F9),UIColorFromRGB(0x45A3FC),UIColorFromRGB(0xA6A9FF),UIColorFromRGB(0x47CBCA),UIColorFromRGB(0xFFC491),UIColorFromRGB(0xF0657D),UIColorFromRGB(0xCC8CF5),UIColorFromRGB(0xB7E89E),UIColorFromRGB(0xFF7F74), nil];
}
return _chartColors;
}
/**
获取扇形上中点坐标
@param arc 占有率
@return 坐标 CGPoint
*/
- (CGPoint)getArcMidPoint:(float)arc
{
CGFloat angle = M_PI*2.0*arc;//将进度转换成弧度
float radius = 75;//半径
int index = (angle)/M_PI_2;//用户区分在第几象限内
float needAngle = angle - index*M_PI_2;//用于计算正弦/余弦的角度
float x = 0,y = 0;
switch (index) {
case 3:
// NSLog(@"第一象限");
x = self.frame.size.width/2 + sinf(needAngle)*radius;
y = self.frame.size.height/2 - cosf(needAngle)*radius;
break;
case 0:
// NSLog(@"第二象限");
x = self.frame.size.width/2 + cosf(needAngle)*radius;
y = self.frame.size.height/2 + sinf(needAngle)*radius;
break;
case 1:
// NSLog(@"第三象限");
x = self.frame.size.width/2 - sinf(needAngle)*radius;
y = self.frame.size.height/2 + cosf(needAngle)*radius;
break;
case 2:
// NSLog(@"第四象限");
x = self.frame.size.width/2 - cosf(needAngle)*radius;
y = self.frame.size.height/2 - sinf(needAngle)*radius;
break;
default:
break;
}
return CGPointMake(x, y);
}
/**
获取扇形上直线坐标
@param arc 占有率
@return 坐标 CGPoint
*/
- (CGPoint)getArcEndMidPoint:(float)arc district:(BOOL)isDistrict endRadius:(CGFloat)endRadius
{
CGFloat angle = M_PI*2.0*arc;//将进度转换成弧度
float radius = 75+endRadius;//半径
int index = (angle)/M_PI_2;//用户区分在第几象限内
float needAngle = angle - index*M_PI_2;//用于计算正弦/余弦的角度
float x = 0,y = 0;
switch (index) {
case 3:
// NSLog(@"第一象限");
if (isDistrict) {
x = self.frame.size.width/2 + sinf(needAngle)*radius;
y = self.frame.size.height/2 - cosf(needAngle)*radius;
}
else {
x = self.frame.size.width/2 + sinf(needAngle)*radius+5;
y = self.frame.size.height/2 - cosf(needAngle)*radius+5;
}
break;
case 0:
// NSLog(@"第二象限");
if (isDistrict) {
x = self.frame.size.width/2 + cosf(needAngle)*radius;
y = self.frame.size.height/2 + sinf(needAngle)*radius;
}
else {
x = self.frame.size.width/2 + cosf(needAngle)*radius+10;
y = self.frame.size.height/2 + sinf(needAngle)*radius-10;
}
break;
case 1:
// NSLog(@"第三象限");
if (isDistrict) {
x = self.frame.size.width/2 - sinf(needAngle)*radius;
y = self.frame.size.height/2 + cosf(needAngle)*radius;
}
else {
x = self.frame.size.width/2 - sinf(needAngle)*radius-10;
y = self.frame.size.height/2 + cosf(needAngle)*radius-10;
}
break;
case 2:
// NSLog(@"第四象限");
if (isDistrict) {
x = self.frame.size.width/2 - cosf(needAngle)*radius;
y = self.frame.size.height/2 - sinf(needAngle)*radius;
}
else {
x = self.frame.size.width/2 - cosf(needAngle)*radius-10;
y = self.frame.size.height/2 - sinf(needAngle)*radius+10;
}
break;
default:
break;
}
return CGPointMake(x, y);
}
/**
获取二次贝塞尔曲线控制点
@param arc 占有率
@return CGPoint
*/
- (CGPoint)getArcControlMidPoint:(float)arc
{
CGFloat angle = M_PI*2.0*arc;//将进度转换成弧度
float radius = 75+10;//半径
int index = (angle)/M_PI_2;//用户区分在第几象限内
float needAngle = angle - index*M_PI_2;//用于计算正弦/余弦的角度
float x = 0,y = 0;
switch (index) {
case 3:
// NSLog(@"第一象限");
x = self.frame.size.width/2 + sinf(needAngle)*radius;
y = self.frame.size.height/2 - cosf(needAngle)*radius;
break;
case 0:
// NSLog(@"第二象限");
x = self.frame.size.width/2 + cosf(needAngle)*radius;
y = self.frame.size.height/2 + sinf(needAngle)*radius;
break;
case 1:
// NSLog(@"第三象限");
x = self.frame.size.width/2 - sinf(needAngle)*radius;
y = self.frame.size.height/2 + cosf(needAngle)*radius;
break;
case 2:
// NSLog(@"第四象限");
x = self.frame.size.width/2 - cosf(needAngle)*radius;
y = self.frame.size.height/2 - sinf(needAngle)*radius;
break;
default:
break;
}
return CGPointMake(x, y);
}
/**
判断线是否做弯曲处理
@param rate 占有率
@return BOOL
*/
- (BOOL)isDistrctLine:(float)rate{
CGFloat angle = M_PI*2.0*rate;//将进度转换成弧度
if ((angle>0&&angle<=M_PI_4)||(angle>M_PI_4+M_PI_2&&angle<=M_PI+M_PI_4)||(angle>M_PI_4+M_PI_2+M_PI&&angle<=M_PI*2)) {
return YES;
}
return NO;
}
- (NSInteger)getCurrentIndex:(CGPoint)point{
for (int i=0; i<[_chartPaths count]; i++) {
if ([_chartPaths[i] containsPoint:point]) {
return i;
}
}
return -1;
}
- (float)getMidRate:(NSInteger)index{
float currentRate = 0 ;
for (int i = 0 ; i<index+1; i++) {
CGFloat rate = [_delegate getChartRate:i];
if (i==index) {
currentRate = rate/2 + currentRate;
return currentRate ;
}
currentRate = rate + currentRate;
}
return currentRate;
}
- (float)getRate:(NSUInteger)index{
float currentRate = 0 ;
for (int i = 0 ; i<index+1; i++) {
CGFloat rate = [_delegate getChartRate:i];
if (i==index) {
currentRate = rate + currentRate;
return currentRate ;
}
currentRate = rate + currentRate;
}
return currentRate;
}
- (void)taphandle:(UITapGestureRecognizer *)sender{
CGPoint touchPoint = [sender locationInView:self];
NSInteger oldIndex = _currentIndex;
_currentIndex = [self getCurrentIndex:touchPoint];
if (_currentIndex>=0) {
if (self.animateBackLayer) {
[self.animateBackLayer removeFromSuperlayer];
}
if (oldIndex>=0) {
_chartLabels[oldIndex].textColor = UIColorFromRGB(0x999999);
_chartLabels[oldIndex].font = [UIFont systemFontOfSize:AdapterF(8)];
CGPoint midPoint = [self getArcMidPoint:[self getMidRate:oldIndex]];
CGPoint endlinePoint = [self getArcEndMidPoint:[self getMidRate:oldIndex] district:[self isDistrctLine:[self getMidRate:oldIndex]] endRadius:20];
[_chartLabels[oldIndex] mas_remakeConstraints:^(MASConstraintMaker *make) {
if (midPoint.x>endlinePoint.x) {
make.right.mas_equalTo(-(self.frame.size.width-endlinePoint.x)-8);
//make.left.mas_equalTo(0);
}
else {
make.left.mas_equalTo(endlinePoint.x+8);
// make.right.mas_equalTo(0);
}
make.top.mas_equalTo(endlinePoint.y-8);
make.height.mas_equalTo(25);
make.width.mas_equalTo(_labelWidths[oldIndex]);
}];
}
if (!(oldIndex ==_currentIndex&&_isShowAnimate)) {
_chartLabels[_currentIndex].textColor = _chartRanColors[_currentIndex];
_chartLabels[_currentIndex].font = [UIFont boldSystemFontOfSize:AdapterF(9)];
[self bringSubviewToFront:_chartLabels[_currentIndex]];
}
UIBezierPath *shadowPath = [UIBezierPath bezierPath];
[shadowPath moveToPoint:self.center];
//如果目标layer小于8度则shadow用本身
if((M_PI*2*([self getRate:_currentIndex]-[_delegate getChartRate:_currentIndex])+(M_PI*4/180))<M_PI*2*[self getRate:_currentIndex]-(M_PI*4/180)){
[shadowPath addArcWithCenter:self.center radius:79 startAngle:(M_PI*2*([self getRate:_currentIndex]-[_delegate getChartRate:_currentIndex])+(M_PI*2/180)) endAngle:M_PI*2*[self getRate:_currentIndex]-(M_PI*2/180) clockwise:YES];//和layer缩小4度
}
else {
[shadowPath addArcWithCenter:self.center radius:79 startAngle:(M_PI*2*([self getRate:_currentIndex]-[_delegate getChartRate:_currentIndex])) endAngle:M_PI*2*[self getRate:_currentIndex] clockwise:YES];
}
_animateBackLayer = [[CAShapeLayer alloc]init];
_animateBackLayer.frame = self.bounds;
_animateBackLayer.fillColor = [[UIColor whiteColor] CGColor];
_animateBackLayer.path = [_chartPaths[_currentIndex] CGPath];
CAShapeLayer* _animateBackLineLayer = [[CAShapeLayer alloc]init];
_animateBackLineLayer.lineWidth = 2;
_animateBackLineLayer.frame = self.bounds;
_animateBackLineLayer.fillColor = [[UIColor whiteColor] CGColor];
_animateBackLineLayer.strokeColor = [[UIColor whiteColor] CGColor];
_animateBackLineLayer.path = [_chartLinePaths[_currentIndex] CGPath];
CAShapeLayer *animateLayer = [[CAShapeLayer alloc]init];
animateLayer.frame = self.bounds;
animateLayer.path = [_chartPaths[_currentIndex] CGPath];
animateLayer.fillColor = [_chartRanColors[_currentIndex] CGColor];
CAShapeLayer *animateShadowLayer = [[CAShapeLayer alloc]init];
animateShadowLayer.frame = self.bounds;
animateShadowLayer.path = [shadowPath CGPath];
animateShadowLayer.fillColor = [_chartRanColors[_currentIndex] CGColor];
animateShadowLayer.opacity = 0.3;
CAShapeLayer *animateLineLayer = [[CAShapeLayer alloc]init];
animateLineLayer.fillColor = [[UIColor whiteColor] CGColor];
animateLineLayer.frame = self.bounds;
animateLineLayer.path = [_chartLinePaths[_currentIndex] CGPath];
animateLineLayer.strokeColor = [_chartRanColors[_currentIndex] CGColor];
[_animateBackLayer addSublayer:_animateBackLineLayer];
[_animateBackLayer addSublayer:animateShadowLayer];
[_animateBackLayer addSublayer:animateLayer];
[_animateBackLayer addSublayer:animateLineLayer];
[self.layer insertSublayer:self.animateBackLayer atIndex:0];
CGPoint endPoint = [self getArcEndMidPoint:[self getMidRate:_currentIndex] district:YES endRadius:7];
CGPoint startPoint = [self getArcMidPoint:[self getMidRate:_currentIndex]];
CABasicAnimation *ani = [[CABasicAnimation alloc]init];
ani.fillMode = kCAFillModeForwards;
ani.keyPath = @"position";
if (oldIndex == _currentIndex && _isShowAnimate) {
ani.toValue = [NSValue valueWithCGPoint:self.center];
}else {
ani.fromValue = [NSValue valueWithCGPoint:self.center];
ani.toValue = [NSValue valueWithCGPoint:CGPointMake(self.center.x+endPoint.x-startPoint.x, self.center.y+endPoint.y-startPoint.y)];
}
ani.delegate = self;
ani.duration = 0.1;
ani.removedOnCompletion = NO;
[animateLayer addAnimation:ani forKey:nil];
endPoint = [self getArcEndMidPoint:[self getMidRate:_currentIndex] district:YES endRadius:11];
startPoint = [self getArcMidPoint:[self getMidRate:_currentIndex]];
CABasicAnimation *lineAni = [[CABasicAnimation alloc]init];
lineAni.fillMode = kCAFillModeForwards;
lineAni.keyPath = @"position";
if (oldIndex == _currentIndex && _isShowAnimate) {
lineAni.toValue = [NSValue valueWithCGPoint:self.center];
}else {
lineAni.fromValue = [NSValue valueWithCGPoint:self.center];
lineAni.toValue = [NSValue valueWithCGPoint:CGPointMake(self.center.x+endPoint.x-startPoint.x, self.center.y+endPoint.y-startPoint.y)];
}
lineAni.delegate = self;
lineAni.duration = 0.1;
lineAni.removedOnCompletion = NO;
[animateLineLayer addAnimation:lineAni forKey:nil];
[animateShadowLayer addAnimation:ani forKey:nil];
CGPoint midPoint = [self getArcMidPoint:[self getMidRate:_currentIndex]];
CGPoint endlinePoint = (oldIndex == _currentIndex && _isShowAnimate)?[self getArcEndMidPoint:[self getMidRate:_currentIndex] district:[self isDistrctLine:[self getMidRate:_currentIndex]] endRadius:20]:[self getArcEndMidPoint:[self getMidRate:_currentIndex] district:[self isDistrctLine:[self getMidRate:_currentIndex]] endRadius:30];
CGFloat rateWidth = [[APPManager defaultManager] getTextWidth:[NSString stringWithFormat:@"%.2f%%",[_delegate getChartRate:_currentIndex]*100] setHeight:30 setFont:[UIFont systemFontOfSize:AdapterF(10)]];
NSString *name = [NSString stringWithFormat:@"%@\n%.2f%%",[_delegate getChartName:_currentIndex],[_delegate getChartRate:_currentIndex]*100];
CGFloat labelWidth = [[APPManager defaultManager] getTextWidth:name setHeight:30 setFont:[UIFont systemFontOfSize:AdapterF(10)]];
if (labelWidth<rateWidth) {
labelWidth = rateWidth;
}
if (midPoint.x>endlinePoint.x) {
if (endPoint.x-23<labelWidth) {
labelWidth = endlinePoint.x-23;
}
}
else {
if (self.frame.size.width-endlinePoint.x-23<labelWidth) {
labelWidth = self.frame.size.width-endPoint.x-23;
}
}
[_chartLabels[_currentIndex] mas_remakeConstraints:^(MASConstraintMaker *make) {
if (midPoint.x>endlinePoint.x) {
make.right.mas_equalTo(-(self.frame.size.width-endlinePoint.x)-8);
//make.left.mas_equalTo(0);
}
else {
make.left.mas_equalTo(endlinePoint.x+8);
// make.right.mas_equalTo(0);
}
make.top.mas_equalTo(endlinePoint.y-15);
make.height.mas_equalTo(30);
make.width.mas_equalTo(labelWidth);
}];
[UIView animateWithDuration:0.1 animations:^{
[self layoutIfNeeded];
}];
if (oldIndex == _currentIndex && _isShowAnimate) {
_isShowAnimate = false;
_currentIndex = -1;
}else {
_isShowAnimate = true;
}
}
else {
_currentIndex = oldIndex;
}
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
if (flag&&!_isShowAnimate) {
[self.animateBackLayer removeFromSuperlayer];
self.animateBackLayer = nil;
}
}
@end