// // PNBarChart.m // PNChartDemo // // Created by kevin on 11/7/13. // Copyright (c) 2013年 kevinzhow. All rights reserved. // #import "PNBarChart.h" #import "PNColor.h" #import "PNChartLabel.h" @interface PNBarChart () { NSMutableArray *_xChartLabels; NSMutableArray *_yChartLabels; } - (UIColor *)barColorAtIndex:(NSUInteger)index; @end @implementation PNBarChart - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { [self setupDefaultValues]; } return self; } - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self setupDefaultValues]; } return self; } - (void)setupDefaultValues { [super setupDefaultValues]; self.backgroundColor = [UIColor whiteColor]; self.clipsToBounds = YES; _showLabel = YES; _barBackgroundColor = PNLightGrey; _labelTextColor = [UIColor grayColor]; _labelFont = [UIFont systemFontOfSize:11.0f]; _xChartLabels = [NSMutableArray array]; _yChartLabels = [NSMutableArray array]; _bars = [NSMutableArray array]; _xLabelSkip = 1; _yLabelSum = 4; _labelMarginTop = 2; _chartMarginLeft = 25.0; _chartMarginRight = 25.0; _chartMarginTop = 25.0; _chartMarginBottom = 25.0; _barRadius = 2.0; _showChartBorder = NO; _chartBorderColor = PNLightGrey; _showLevelLine = NO; _yChartLabelWidth = 18; _rotateForXAxisText = false; _isGradientShow = YES; _isShowNumbers = YES; _yLabelPrefix = @""; _yLabelSuffix = @""; _yLabelFormatter = ^(CGFloat yValue){ return [NSString stringWithFormat:@"%1.f",yValue]; }; } - (void)setYValues:(NSArray *)yValues { _yValues = yValues; //make the _yLabelSum value dependant of the distinct values of yValues to avoid duplicates on yAxis if (_showLabel) { [self __addYCoordinateLabelsValues]; } else { [self processYMaxValue]; } } - (void)processYMaxValue { NSArray *yAxisValues = _yLabels ? _yLabels : _yValues; _yLabelSum = _yLabels ? _yLabels.count - 1 :_yLabelSum; if (_yMaxValue) { _yValueMax = _yMaxValue; } else { [self getYValueMax:yAxisValues]; } if (_yLabelSum==4) { _yLabelSum = yAxisValues.count; (_yLabelSum % 2 == 0) ? _yLabelSum : _yLabelSum++; } } #pragma mark - Private Method #pragma mark - Add Y Label - (void)__addYCoordinateLabelsValues{ [self viewCleanupForCollection:_yChartLabels]; [self processYMaxValue]; float sectionHeight = (self.frame.size.height - _chartMarginTop - _chartMarginBottom - kXLabelHeight) / _yLabelSum; for (int i = 0; i <= _yLabelSum; i++) { NSString *labelText; if (_yLabels) { float yAsixValue = [_yLabels[_yLabels.count - i - 1] floatValue]; labelText= _yLabelFormatter(yAsixValue); } else { labelText = _yLabelFormatter((float)_yValueMax * ( (_yLabelSum - i) / (float)_yLabelSum )); } PNChartLabel *label = [[PNChartLabel alloc] initWithFrame:CGRectZero]; label.font = _labelFont; label.textColor = _labelTextColor; [label setTextAlignment:NSTextAlignmentRight]; label.text = [NSString stringWithFormat:@"%@%@%@", _yLabelPrefix, labelText, _yLabelSuffix]; [self addSubview:label]; label.frame = (CGRect){0, sectionHeight * i + _chartMarginTop - kYLabelHeight/2.0 + kXLabelHeight + _labelMarginTop, _yChartLabelWidth, kYLabelHeight}; [_yChartLabels addObject:label]; } } -(void)updateChartData:(NSArray *)data{ self.yValues = data; [self updateBar]; } - (void)getYValueMax:(NSArray *)yLabels { CGFloat max = [[yLabels valueForKeyPath:@"@max.floatValue"] floatValue]; //ensure max is even _yValueMax = max ; if (_yValueMax == 0) { _yValueMax = _yMinValue; } } - (void)setXLabels:(NSArray *)xLabels { _xLabels = xLabels; if (_xChartLabels) { [self viewCleanupForCollection:_xChartLabels]; }else{ _xChartLabels = [NSMutableArray new]; } _xLabelWidth = (self.frame.size.width - _chartMarginLeft - _chartMarginRight) / [xLabels count]; if (_showLabel) { int labelAddCount = 0; for (int index = 0; index < _xLabels.count; index++) { labelAddCount += 1; if (labelAddCount == _xLabelSkip) { NSString *labelText = [_xLabels[index] description]; PNChartLabel * label = [[PNChartLabel alloc] initWithFrame:CGRectMake(0, 0, _xLabelWidth, kXLabelHeight)]; label.font = _labelFont; label.textColor = _labelTextColor; [label setTextAlignment:NSTextAlignmentCenter]; label.text = labelText; //[label sizeToFit]; CGFloat labelXPosition; if (_rotateForXAxisText){ label.transform = CGAffineTransformMakeRotation(M_PI / 4); labelXPosition = (index * _xLabelWidth + _chartMarginLeft + _xLabelWidth /1.5); } else{ labelXPosition = (index * _xLabelWidth + _chartMarginLeft + _xLabelWidth /2.0 ); } label.center = CGPointMake(labelXPosition, self.frame.size.height - _chartMarginTop + label.frame.size.height /2.0 + _labelMarginTop); labelAddCount = 0; [_xChartLabels addObject:label]; [self addSubview:label]; } } } } - (void)setStrokeColor:(UIColor *)strokeColor { _strokeColor = strokeColor; } - (void)updateBar { //Add bars CGFloat chartCavanHeight = self.frame.size.height - _chartMarginTop - _chartMarginBottom - kXLabelHeight; NSInteger index = 0; for (NSNumber *valueString in _yValues) { PNBar *bar; if (_bars.count == _yValues.count) { bar = [_bars objectAtIndex:index]; }else{ CGFloat barWidth; CGFloat barXPosition; if (_barWidth) { barWidth = _barWidth; barXPosition = index * _xLabelWidth + _chartMarginLeft + _xLabelWidth /2.0 - _barWidth /2.0; }else{ barXPosition = index * _xLabelWidth + _chartMarginLeft + _xLabelWidth * 0.25; if (_showLabel) { barWidth = _xLabelWidth * 0.5; } else { barWidth = _xLabelWidth * 0.6; } } bar = [[PNBar alloc] initWithFrame:CGRectMake(barXPosition, //Bar X position self.frame.size.height - chartCavanHeight - kXLabelHeight - _chartMarginBottom + _chartMarginTop , //Bar Y position barWidth, // Bar witdh self.showLevelLine ? chartCavanHeight/2.0:chartCavanHeight)]; //Bar height //Change Bar Radius bar.barRadius = _barRadius; //Set Bar Animation bar.displayAnimated = self.displayAnimated; //Change Bar Background color bar.backgroundColor = _barBackgroundColor; //Bar StrokColor First if (self.strokeColor) { bar.barColor = self.strokeColor; }else{ bar.barColor = [self barColorAtIndex:index]; } if (self.labelTextColor) { bar.labelTextColor = self.labelTextColor; } // Add gradient if (self.isGradientShow) { bar.barColorGradientStart = bar.barColor; } //For Click Index bar.tag = index; [_bars addObject:bar]; [self addSubview:bar]; } //Height Of Bar float value = [valueString floatValue]; float grade =fabsf((float)value / (float)_yValueMax); if (isnan(grade)) { grade = 0; } bar.maxDivisor = (float)_yValueMax; bar.grade = grade; bar.isShowNumber = self.isShowNumbers; CGRect originalFrame = bar.frame; NSString *currentNumber = [NSString stringWithFormat:@"%f",value]; if ([[currentNumber substringToIndex:1] isEqualToString:@"-"] && self.showLevelLine) { CGAffineTransform transform =CGAffineTransformMakeRotation(M_PI); [bar setTransform:transform]; originalFrame.origin.y = bar.frame.origin.y + bar.frame.size.height; bar.frame = originalFrame; bar.isNegative = YES; } index += 1; } } - (void)strokeChart { //Add Labels [self viewCleanupForCollection:_bars]; //Update Bar [self updateBar]; //Add chart border lines if (_showChartBorder) { _chartBottomLine = [CAShapeLayer layer]; _chartBottomLine.lineCap = kCALineCapButt; _chartBottomLine.fillColor = [[UIColor whiteColor] CGColor]; _chartBottomLine.lineWidth = 1.0; _chartBottomLine.strokeEnd = 0.0; UIBezierPath *progressline = [UIBezierPath bezierPath]; [progressline moveToPoint:CGPointMake(_chartMarginLeft, self.frame.size.height - kXLabelHeight - _chartMarginBottom + _chartMarginTop)]; [progressline addLineToPoint:CGPointMake(self.frame.size.width - _chartMarginRight, self.frame.size.height - kXLabelHeight - _chartMarginBottom + _chartMarginTop)]; [progressline setLineWidth:1.0]; [progressline setLineCapStyle:kCGLineCapSquare]; _chartBottomLine.path = progressline.CGPath; _chartBottomLine.strokeColor = [_chartBorderColor CGColor];; _chartBottomLine.strokeEnd = 1.0; [self.layer addSublayer:_chartBottomLine]; //Add left Chart Line _chartLeftLine = [CAShapeLayer layer]; _chartLeftLine.lineCap = kCALineCapButt; _chartLeftLine.fillColor = [[UIColor whiteColor] CGColor]; _chartLeftLine.lineWidth = 1.0; _chartLeftLine.strokeEnd = 0.0; UIBezierPath *progressLeftline = [UIBezierPath bezierPath]; [progressLeftline moveToPoint:CGPointMake(_chartMarginLeft, self.frame.size.height - kXLabelHeight - _chartMarginBottom + _chartMarginTop)]; [progressLeftline addLineToPoint:CGPointMake(_chartMarginLeft, _chartMarginTop)]; [progressLeftline setLineWidth:1.0]; [progressLeftline setLineCapStyle:kCGLineCapSquare]; _chartLeftLine.path = progressLeftline.CGPath; _chartLeftLine.strokeColor = [_chartBorderColor CGColor]; _chartLeftLine.strokeEnd = 1.0; [self addBorderAnimationIfNeeded]; [self.layer addSublayer:_chartLeftLine]; } // Add Level Separator Line if (_showLevelLine) { _chartLevelLine = [CAShapeLayer layer]; _chartLevelLine.lineCap = kCALineCapButt; _chartLevelLine.fillColor = [[UIColor whiteColor] CGColor]; _chartLevelLine.lineWidth = 1.0; _chartLevelLine.strokeEnd = 0.0; UIBezierPath *progressline = [UIBezierPath bezierPath]; [progressline moveToPoint:CGPointMake(_chartMarginLeft, (self.frame.size.height - kXLabelHeight )/2.0)]; [progressline addLineToPoint:CGPointMake(self.frame.size.width - _chartMarginLeft - _chartMarginRight, (self.frame.size.height - kXLabelHeight )/2.0)]; [progressline setLineWidth:1.0]; [progressline setLineCapStyle:kCGLineCapSquare]; _chartLevelLine.path = progressline.CGPath; _chartLevelLine.strokeColor = PNLightGrey.CGColor; [self addSeparatorAnimationIfNeeded]; _chartLevelLine.strokeEnd = 1.0; [self.layer addSublayer:_chartLevelLine]; } else { if (_chartLevelLine) { [_chartLevelLine removeFromSuperlayer]; _chartLevelLine = nil; } } } - (void)addBorderAnimationIfNeeded { if (self.displayAnimated) { CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; pathAnimation.duration = 0.5; pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; pathAnimation.fromValue = @0.0f; pathAnimation.toValue = @1.0f; [_chartBottomLine addAnimation:pathAnimation forKey:@"strokeEndAnimation"]; CABasicAnimation *pathLeftAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; pathLeftAnimation.duration = 0.5; pathLeftAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; pathLeftAnimation.fromValue = @0.0f; pathLeftAnimation.toValue = @1.0f; [_chartLeftLine addAnimation:pathLeftAnimation forKey:@"strokeEndAnimation"]; } } - (void)addSeparatorAnimationIfNeeded { if (self.displayAnimated) { CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; pathAnimation.duration = 0.5; pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; pathAnimation.fromValue = @0.0f; pathAnimation.toValue = @1.0f; [_chartLevelLine addAnimation:pathAnimation forKey:@"strokeEndAnimation"]; } } - (void)viewCleanupForCollection:(NSMutableArray *)array { if (array.count) { [array makeObjectsPerformSelector:@selector(removeFromSuperview)]; [array removeAllObjects]; } } #pragma mark - Class extension methods - (UIColor *)barColorAtIndex:(NSUInteger)index { if ([self.strokeColors count] == [self.yValues count]) { return self.strokeColors[index]; } else { return self.strokeColor; } } #pragma mark - Touch detection - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self touchPoint:touches withEvent:event]; [super touchesBegan:touches withEvent:event]; } - (void)touchPoint:(NSSet *)touches withEvent:(UIEvent *)event { //Get the point user touched UITouch *touch = [touches anyObject]; CGPoint touchPoint = [touch locationInView:self]; UIView *subview = [self hitTest:touchPoint withEvent:nil]; if ([subview isKindOfClass:[PNBar class]] && [self.delegate respondsToSelector:@selector(userClickedOnBarAtIndex:)]) { [self.delegate userClickedOnBarAtIndex:subview.tag]; } } @end