// // PNRadarChart.m // PNChartDemo // // Created by Lei on 15/7/1. // Copyright (c) 2015年 kevinzhow. All rights reserved. // #import "PNRadarChart.h" @interface PNRadarChart() @property (nonatomic) CGFloat centerX; @property (nonatomic) CGFloat centerY; @property (nonatomic) NSMutableArray *pointsToWebArrayArray; @property (nonatomic) NSMutableArray *pointsToPlotArray; @property (nonatomic) UILabel *detailLabel; @property (nonatomic) CGFloat lengthUnit; @property (nonatomic) CAShapeLayer *chartPlot; @end @implementation PNRadarChart - (id)initWithFrame:(CGRect)frame items:(NSArray *)items valueDivider:(CGFloat)unitValue { self=[super initWithFrame:frame]; if (self) { self.backgroundColor = [UIColor clearColor]; self.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; //Public iVar if ([items count]< 3)//At least three corners of A polygon ,If the count of items is less than 3 will add 3 default values { NSLog( @"At least three items!"); NSArray *defaultArray = @[[PNRadarChartDataItem dataItemWithValue:0 description:@"Default"], [PNRadarChartDataItem dataItemWithValue:0 description:@"Default"], [PNRadarChartDataItem dataItemWithValue:0 description:@"Default"], ]; defaultArray = [defaultArray arrayByAddingObjectsFromArray:items]; _chartData = [NSArray arrayWithArray:defaultArray]; }else{ _chartData = [NSArray arrayWithArray:items]; } _valueDivider = unitValue; _maxValue = 1; _webColor = [UIColor grayColor]; _plotColor = [UIColor colorWithRed:.4 green:.8 blue:.4 alpha:.7]; _fontColor = [UIColor blackColor]; _graduationColor = [UIColor orangeColor]; _fontSize = 15; _labelStyle = PNRadarChartLabelStyleHorizontal; _isLabelTouchable = YES; _isShowGraduation = NO; //Private iVar _centerX = frame.size.width/2; _centerY = frame.size.height/2; _pointsToWebArrayArray = [NSMutableArray array]; _pointsToPlotArray = [NSMutableArray array]; _lengthUnit = 0; _chartPlot = [CAShapeLayer layer]; _chartPlot.lineCap = kCALineCapButt; _chartPlot.lineWidth = 1.0; [self.layer addSublayer:_chartPlot]; [super setupDefaultValues]; //init detailLabel _detailLabel = [[UILabel alloc] init]; _detailLabel.backgroundColor = [UIColor colorWithRed:.9 green:.9 blue:.1 alpha:.9]; _detailLabel.textAlignment = NSTextAlignmentCenter; _detailLabel.textColor = [UIColor colorWithWhite:1 alpha:1]; _detailLabel.font = [UIFont systemFontOfSize:15]; [_detailLabel setHidden:YES]; [self addSubview:_detailLabel]; [self strokeChart]; } return self; } #pragma mark - main - (void)calculateChartPoints { [_pointsToPlotArray removeAllObjects]; [_pointsToWebArrayArray removeAllObjects]; //init Descriptions , Values and Angles. NSMutableArray *descriptions = [NSMutableArray array]; NSMutableArray *values = [NSMutableArray array]; NSMutableArray *angles = [NSMutableArray array]; for (int i=0;i<_chartData.count;i++) { PNRadarChartDataItem *item = (PNRadarChartDataItem *)[_chartData objectAtIndex:i]; [descriptions addObject:item.textDescription]; [values addObject:[NSNumber numberWithFloat:item.value]]; CGFloat angleValue = (float)i/(float)[_chartData count]*2*M_PI; [angles addObject:[NSNumber numberWithFloat:angleValue]]; } //calculate all the lengths _maxValue = [self getMaxValueFromArray:values]; CGFloat margin = 0; if (_labelStyle==PNRadarChartLabelStyleCircle) { margin = MIN(_centerX , _centerY)*3/10; }else if (_labelStyle==PNRadarChartLabelStyleHorizontal) { margin = [self getMaxWidthLabelFromArray:descriptions withFontSize:_fontSize]; } CGFloat maxLength = ceil(MIN(_centerX, _centerY) - margin); int plotCircles = (_maxValue/_valueDivider); if (plotCircles > MAXCIRCLE) { NSLog(@"Circle number is higher than max"); plotCircles = MAXCIRCLE; _valueDivider = _maxValue/plotCircles; } _lengthUnit = maxLength/plotCircles; NSArray *lengthArray = [self getLengthArrayWithCircleNum:(int)plotCircles]; //get all the points and plot for (NSNumber *lengthNumber in lengthArray) { CGFloat length = [lengthNumber floatValue]; [_pointsToWebArrayArray addObject:[self getWebPointWithLength:length angleArray:angles]]; } int section = 0; for (id value in values) { CGFloat valueFloat = [value floatValue]; if (valueFloat>_maxValue) { NSString *reason = [NSString stringWithFormat:@"Value number is higher than max -value: %f - maxValue: %f",valueFloat,_maxValue]; @throw [NSException exceptionWithName:NSInvalidArgumentException reason:reason userInfo:nil]; return; } CGFloat length = valueFloat/_maxValue*maxLength; CGFloat angle = [[angles objectAtIndex:section] floatValue]; CGFloat x = _centerX +length*cos(angle); CGFloat y = _centerY +length*sin(angle); NSValue* point = [NSValue valueWithCGPoint:CGPointMake(x, y)]; [_pointsToPlotArray addObject:point]; section++; } //set the labels [self drawLabelWithMaxLength:maxLength labelArray:descriptions angleArray:angles]; } #pragma mark - Draw - (void)drawRect:(CGRect)rect { // Drawing backgound CGContextRef context = UIGraphicsGetCurrentContext(); CGContextClearRect(context, rect); int section = 0; //circles for(NSArray *pointArray in _pointsToWebArrayArray){ //plot backgound CGContextRef graphContext = UIGraphicsGetCurrentContext(); CGContextBeginPath(graphContext); CGPoint beginPoint = [[pointArray objectAtIndex:0] CGPointValue]; CGContextMoveToPoint(graphContext, beginPoint.x, beginPoint.y); for(NSValue* pointValue in pointArray){ CGPoint point = [pointValue CGPointValue]; CGContextAddLineToPoint(graphContext, point.x, point.y); } CGContextAddLineToPoint(graphContext, beginPoint.x, beginPoint.y); CGContextSetStrokeColorWithColor(graphContext, _webColor.CGColor); CGContextStrokePath(graphContext); } //cuts NSArray *largestPointArray = [_pointsToWebArrayArray lastObject]; for (NSValue *pointValue in largestPointArray){ section++; if (section==1&&_isShowGraduation)continue; CGContextRef graphContext = UIGraphicsGetCurrentContext(); CGContextBeginPath(graphContext); CGContextMoveToPoint(graphContext, _centerX, _centerY); CGPoint point = [pointValue CGPointValue]; CGContextAddLineToPoint(graphContext, point.x, point.y); CGContextSetStrokeColorWithColor(graphContext, _webColor.CGColor); CGContextStrokePath(graphContext); } } - (void)strokeChart { [self calculateChartPoints]; [self setNeedsDisplay]; [_detailLabel setHidden:YES]; //Draw plot [_chartPlot removeAllAnimations]; UIBezierPath *plotline = [UIBezierPath bezierPath]; CGPoint beginPoint = [[_pointsToPlotArray objectAtIndex:0] CGPointValue]; [plotline moveToPoint:CGPointMake(beginPoint.x, beginPoint.y)]; for(NSValue *pointValue in _pointsToPlotArray){ CGPoint point = [pointValue CGPointValue]; [plotline addLineToPoint:CGPointMake(point.x ,point.y)]; } [plotline setLineWidth:1]; [plotline setLineCapStyle:kCGLineCapButt]; _chartPlot.path = plotline.CGPath; _chartPlot.fillColor = _plotColor.CGColor; [self addAnimationIfNeeded]; [self showGraduation]; } #pragma mark - Helper - (void)drawLabelWithMaxLength:(CGFloat)maxLength labelArray:(NSArray *)labelArray angleArray:(NSArray *)angleArray { //set labels int labelTag = 121; while (true) { UIView *label = [self viewWithTag:labelTag]; if(!label)break; [label removeFromSuperview]; } int section = 0; CGFloat labelLength = maxLength + maxLength/10; for (NSString *labelString in labelArray) { CGFloat angle = [[angleArray objectAtIndex:section] floatValue]; CGFloat x = _centerX + labelLength *cos(angle); CGFloat y = _centerY + labelLength *sin(angle); UILabel *label = [[UILabel alloc] init] ; label.backgroundColor = [UIColor clearColor]; label.font = [UIFont systemFontOfSize:_fontSize]; label.text = labelString; label.tag = labelTag; CGSize detailSize = [labelString sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:_fontSize]}]; switch (_labelStyle) { case PNRadarChartLabelStyleCircle: label.frame = CGRectMake(x-5*_fontSize/2, y-_fontSize/2, 5*_fontSize, _fontSize); label.transform = CGAffineTransformMakeRotation(((float)section/[labelArray count])*(2*M_PI)+M_PI_2); label.textAlignment = NSTextAlignmentCenter; break; case PNRadarChartLabelStyleHorizontal: if (x<_centerX) { label.frame = CGRectMake(x-detailSize.width, y-detailSize.height/2, detailSize.width, detailSize.height); label.textAlignment = NSTextAlignmentRight; }else{ label.frame = CGRectMake(x, y-detailSize.height/2, detailSize.width , detailSize.height); label.textAlignment = NSTextAlignmentLeft; } break; case PNRadarChartLabelStyleHidden: [label setHidden:YES]; break; default: break; } [label sizeToFit]; label.userInteractionEnabled = YES; UITapGestureRecognizer *tapLabelGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapLabel:)]; [label addGestureRecognizer:tapLabelGesture]; [self addSubview:label]; section ++; } } - (void)tapLabel:(UITapGestureRecognizer *)recognizer { UILabel *label=(UILabel*)recognizer.view; _detailLabel.frame = CGRectMake(label.frame.origin.x, label.frame.origin.y-30, 50, 25); for (PNRadarChartDataItem *item in _chartData) { if ([label.text isEqualToString:item.textDescription]) { _detailLabel.text = [NSString stringWithFormat:@"%.2f", item.value]; break; } } [_detailLabel setHidden:NO]; } - (void)showGraduation { int labelTag = 112; while (true) { UIView *label = [self viewWithTag:labelTag]; if(!label)break; [label removeFromSuperview]; } int section = 0; for (NSArray *pointsArray in _pointsToWebArrayArray) { section++; CGPoint labelPoint = [[pointsArray objectAtIndex:0] CGPointValue]; UILabel *graduationLabel = [[UILabel alloc] initWithFrame:CGRectMake(labelPoint.x-_lengthUnit, labelPoint.y-_lengthUnit*5/8, _lengthUnit*5/8, _lengthUnit)]; graduationLabel.adjustsFontSizeToFitWidth = YES; graduationLabel.tag = labelTag; graduationLabel.font = [UIFont systemFontOfSize:ceil(_lengthUnit)]; graduationLabel.textColor = [UIColor orangeColor]; graduationLabel.text = [NSString stringWithFormat:@"%.0f",_valueDivider*section]; [self addSubview:graduationLabel]; if (_isShowGraduation) { [graduationLabel setHidden:NO]; }else{ [graduationLabel setHidden:YES];} } } - (NSArray *)getWebPointWithLength:(CGFloat)length angleArray:(NSArray *)angleArray { NSMutableArray *pointArray = [NSMutableArray array]; for (NSNumber *angleNumber in angleArray) { CGFloat angle = [angleNumber floatValue]; CGFloat x = _centerX + length*cos(angle); CGFloat y = _centerY + length*sin(angle); [pointArray addObject:[NSValue valueWithCGPoint:CGPointMake(x,y)]]; } return pointArray; } - (NSArray *)getLengthArrayWithCircleNum:(int)plotCircles { NSMutableArray *lengthArray = [NSMutableArray array]; CGFloat length = 0; for (int i = 0; i < plotCircles; i++) { length += _lengthUnit; [lengthArray addObject:[NSNumber numberWithFloat:length]]; } return lengthArray; } - (CGFloat)getMaxWidthLabelFromArray:(NSArray *)keyArray withFontSize:(CGFloat)size { CGFloat maxWidth = 0; for (NSString *str in keyArray) { CGSize detailSize = [str sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:_fontSize]}]; maxWidth = MAX(maxWidth, detailSize.width); } return maxWidth; } - (CGFloat)getMaxValueFromArray:(NSArray *)valueArray { CGFloat max = _maxValue; for (NSNumber *valueNum in valueArray) { CGFloat valueFloat = [valueNum floatValue]; max = MAX(valueFloat, max); } return ceil(max); } - (void)addAnimationIfNeeded { if (self.displayAnimated) { CABasicAnimation *animateScale = [CABasicAnimation animationWithKeyPath:@"transform.scale"]; animateScale.fromValue = [NSNumber numberWithFloat:0.f]; animateScale.toValue = [NSNumber numberWithFloat:1.0f]; CABasicAnimation *animateMove = [CABasicAnimation animationWithKeyPath:@"position"]; animateMove.fromValue = [NSValue valueWithCGPoint:CGPointMake(_centerX, _centerY)]; animateMove.toValue = [NSValue valueWithCGPoint:CGPointMake(0, 0)]; CABasicAnimation *animateAlpha = [CABasicAnimation animationWithKeyPath:@"opacity"]; animateAlpha.fromValue = [NSNumber numberWithFloat:0.f]; CAAnimationGroup *aniGroup = [CAAnimationGroup animation]; aniGroup.duration = 1.f; aniGroup.repeatCount = 1; aniGroup.animations = [NSArray arrayWithObjects:animateScale,animateMove,animateAlpha, nil]; aniGroup.removedOnCompletion = YES; [_chartPlot addAnimation:aniGroup forKey:nil]; } } @end