//
|
// 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
|