新版本

This commit is contained in:
roy
2019-03-12 09:26:46 +08:00
parent cbbfcb098a
commit c08d198e11
431 changed files with 19432 additions and 9136 deletions
+37
View File
@@ -0,0 +1,37 @@
//
// PNBar.h
// PNChartDemo
//
// Created by kevin on 11/7/13.
// Copyright (c) 2013年 kevinzhow. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
@interface PNBar : UIView
- (void)rollBack;
@property (nonatomic) float grade;
@property (nonatomic) float maxDivisor;
@property (nonatomic) CAShapeLayer *chartLine;
@property (nonatomic) UIColor *barColor;
@property (nonatomic) UIColor *barColorGradientStart;
@property (nonatomic) CGFloat barRadius;
@property (nonatomic) CAShapeLayer *gradientMask;
@property (nonatomic) CAShapeLayer *gradeLayer;
@property (nonatomic) CATextLayer* textLayer;
/** Text color for all bars in the chart. */
@property (nonatomic) UIColor * labelTextColor;
@property (nonatomic, assign) BOOL isNegative; //!< 是否是负数
@property (nonatomic, assign) BOOL isShowNumber; //!< 是否显示numbers
/** Display the bar with or without animation. Default is YES. **/
@property (nonatomic) BOOL displayAnimated;
@end
+288
View File
@@ -0,0 +1,288 @@
//
// PNBar.m
// PNChartDemo
//
// Created by kevin on 11/7/13.
// Copyright (c) 2013年 kevinzhow. All rights reserved.
//
#import "PNBar.h"
#import "PNColor.h"
#import <CoreText/CoreText.h>
@interface PNBar ()
@property (nonatomic) float copyGrade;
@end
@implementation PNBar
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_chartLine = [CAShapeLayer layer];
_chartLine.lineCap = kCALineCapButt;
_chartLine.fillColor = [[UIColor whiteColor] CGColor];
_chartLine.lineWidth = self.frame.size.width;
_chartLine.strokeEnd = 0.0;
self.clipsToBounds = YES;
[self.layer addSublayer:_chartLine];
self.barRadius = 2.0;
}
return self;
}
-(void)setBarRadius:(CGFloat)barRadius
{
_barRadius = barRadius;
self.layer.cornerRadius = _barRadius;
}
- (void)setGrade:(float)grade
{
_copyGrade = grade;
CGFloat startPosY = (1 - grade) * self.frame.size.height;
UIBezierPath *progressline = [UIBezierPath bezierPath];
[progressline moveToPoint:CGPointMake(self.frame.size.width / 2.0, self.frame.size.height)];
[progressline addLineToPoint:CGPointMake(self.frame.size.width / 2.0, startPosY)];
[progressline setLineWidth:1.0];
[progressline setLineCapStyle:kCGLineCapSquare];
[self addAnimationIfNeededWithProgressLine:progressline];
if (_barColor) {
_chartLine.strokeColor = [_barColor CGColor];
}
else {
_chartLine.strokeColor = [PNGreen CGColor];
}
if (_grade) {
_chartLine.path = progressline.CGPath;
if (_barColorGradientStart) {
// Add gradient
self.gradientMask.path = progressline.CGPath;
CABasicAnimation* opacityAnimation = [self fadeAnimation];
[self.textLayer addAnimation:opacityAnimation forKey:nil];
}
}else{
_chartLine.strokeEnd = 1.0;
_chartLine.path = progressline.CGPath;
// Check if user wants to add a gradient from the start color to the bar color
if (_barColorGradientStart) {
// Add gradient
self.gradientMask = [CAShapeLayer layer];
self.gradientMask.fillColor = [[UIColor clearColor] CGColor];
self.gradientMask.strokeColor = [[UIColor blackColor] CGColor];
self.gradientMask.lineWidth = self.frame.size.width;
self.gradientMask.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
self.gradientMask.path = progressline.CGPath;
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.startPoint = CGPointMake(0.0,0.0);
gradientLayer.endPoint = CGPointMake(1.0 ,0.0);
gradientLayer.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
UIColor *middleColor = [UIColor colorWithWhite:255/255 alpha:0.8];
NSArray *colors = @[
(__bridge id)self.barColor.CGColor,
(__bridge id)middleColor.CGColor,
(__bridge id)self.barColor.CGColor
];
gradientLayer.colors = colors;
[gradientLayer setMask:self.gradientMask];
[_chartLine addSublayer:gradientLayer];
self.gradientMask.strokeEnd = 1.0;
CABasicAnimation* opacityAnimation = [self fadeAnimation];
[self.textLayer addAnimation:opacityAnimation forKey:nil];
}
}
_grade = grade;
}
- (void)rollBack
{
[UIView animateWithDuration:0.3 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations: ^{
_chartLine.strokeColor = [UIColor clearColor].CGColor;
} completion:nil];
}
- (void)setBarColorGradientStart:(UIColor *)barColorGradientStart
{
// Set gradient color, remove any existing sublayer first
for (CALayer *sublayer in [_chartLine sublayers]) {
[sublayer removeFromSuperlayer];
}
_barColorGradientStart = barColorGradientStart;
[self setGrade:_grade];
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, self.backgroundColor.CGColor);
CGContextFillRect(context, rect);
}
// add number display on the top of bar
-(CGPathRef)gradePath:(CGRect)rect
{
return nil;
}
-(CATextLayer*)textLayer
{
if (!_textLayer) {
_textLayer = [[CATextLayer alloc]init];
[_textLayer setString:@"0"];
[_textLayer setAlignmentMode:kCAAlignmentCenter];
[_textLayer setForegroundColor:[_labelTextColor CGColor]];
_textLayer.hidden = YES;
}
return _textLayer;
}
- (void) setLabelTextColor:(UIColor *)labelTextColor {
_labelTextColor = labelTextColor;
[_textLayer setForegroundColor:[_labelTextColor CGColor]];
}
-(void)setGradeFrame:(CGFloat)grade startPosY:(CGFloat)startPosY
{
CGFloat textheigt = self.bounds.size.height*self.grade;
CGFloat topSpace = self.bounds.size.height * (1-self.grade);
CGFloat textWidth = self.bounds.size.width;
[_chartLine addSublayer:self.textLayer];
[self.textLayer setFontSize:18.0];
[self.textLayer setString:[[NSString alloc]initWithFormat:@"%0.f",grade*self.maxDivisor]];
CGSize size = CGSizeMake(320,2000); //设置一个行高上限
NSDictionary *attributes = @{NSFontAttributeName:[UIFont systemFontOfSize:18.0]};
size = [self.textLayer.string boundingRectWithSize:size options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size;
float verticalY ;
if (size.height>=textheigt) {
verticalY = topSpace - size.height;
} else {
verticalY = topSpace + (textheigt-size.height)/2.0;
}
[self.textLayer setFrame:CGRectMake((textWidth-size.width)/2.0,verticalY, size.width,size.height)];
self.textLayer.contentsScale = [UIScreen mainScreen].scale;
}
- (void)setIsShowNumber:(BOOL)isShowNumber{
if (isShowNumber) {
self.textLayer.hidden = NO;
[self setGradeFrame:_copyGrade startPosY:0];
}else{
self.textLayer.hidden = YES;
}
}
- (void)setIsNegative:(BOOL)isNegative{
if (isNegative) {
[self.textLayer setString:[[NSString alloc]initWithFormat:@"- %1.f",_grade*self.maxDivisor]];
CGSize size = CGSizeMake(320,2000); //设置一个行高上限
NSDictionary *attributes = @{NSFontAttributeName:[UIFont systemFontOfSize:18.0]};
size = [self.textLayer.string boundingRectWithSize:size options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size;
CGRect frame = self.textLayer.frame;
frame.origin.x = (self.bounds.size.width - size.width)/2.0;
frame.size = size;
self.textLayer.frame = frame;
[self addRotationAnimationIfNeeded];
}
}
-(CABasicAnimation*)fadeAnimation
{
CABasicAnimation* fadeAnimation = nil;
if (self.displayAnimated) {
fadeAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeAnimation.fromValue = [NSNumber numberWithFloat:0.0];
fadeAnimation.toValue = [NSNumber numberWithFloat:1.0];
fadeAnimation.duration = 2.0;
}
return fadeAnimation;
}
-(void)addAnimationIfNeededWithProgressLine:(UIBezierPath *)progressline
{
if (self.displayAnimated) {
CABasicAnimation *pathAnimation = nil;
if (_grade) {
pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
pathAnimation.fromValue = (id)_chartLine.path;
pathAnimation.toValue = (id)[progressline CGPath];
pathAnimation.duration = 0.5f;
pathAnimation.autoreverses = NO;
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[_chartLine addAnimation:pathAnimation forKey:@"animationKey"];
}
else {
pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
pathAnimation.duration = 1.0;
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
pathAnimation.fromValue = @0.0f;
pathAnimation.toValue = @1.0f;
[_chartLine addAnimation:pathAnimation forKey:@"strokeEndAnimation"];
}
[self.gradientMask addAnimation:pathAnimation forKey:@"animationKey"];
}
}
- (void)addRotationAnimationIfNeeded
{
if (self.displayAnimated) {
CABasicAnimation* rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
rotationAnimation.toValue = [NSNumber numberWithFloat: M_PI];
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
rotationAnimation.duration = 0.1;
rotationAnimation.repeatCount = 0;//你可以设置到最大的整数值
rotationAnimation.cumulative = NO;
rotationAnimation.removedOnCompletion = NO;
rotationAnimation.fillMode = kCAFillModeForwards;
[self.textLayer addAnimation:rotationAnimation forKey:@"Rotation"];
}
}
@end
+123
View File
@@ -0,0 +1,123 @@
//
// PNBarChart.h
// PNChartDemo
//
// Created by kevin on 11/7/13.
// Copyright (c) 2013年 kevinzhow. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "PNGenericChart.h"
#import "PNChartDelegate.h"
#import "PNBar.h"
#define kXLabelMargin 15
#define kYLabelMargin 15
#define kYLabelHeight 11
#define kXLabelHeight 20
typedef NSString *(^PNYLabelFormatter)(CGFloat yLabelValue);
@interface PNBarChart : PNGenericChart
/**
* Draws the chart in an animated fashion.
*/
- (void)strokeChart;
@property (nonatomic) NSArray *xLabels;
@property (nonatomic) NSArray *yLabels;
@property (nonatomic) NSArray *yValues;
@property (nonatomic) NSMutableArray * bars;
@property (nonatomic) CGFloat xLabelWidth;
@property (nonatomic) float yValueMax;
@property (nonatomic) UIColor *strokeColor;
@property (nonatomic) NSArray *strokeColors;
/** Update Values. */
- (void)updateChartData:(NSArray *)data;
/** Changes chart margin. */
@property (nonatomic) CGFloat yChartLabelWidth;
/** Formats the ylabel text. */
@property (copy) PNYLabelFormatter yLabelFormatter;
/** Prefix to y label values, none if unset. */
@property (nonatomic) NSString *yLabelPrefix;
/** Suffix to y label values, none if unset. */
@property (nonatomic) NSString *yLabelSuffix;
@property (nonatomic) CGFloat chartMarginLeft;
@property (nonatomic) CGFloat chartMarginRight;
@property (nonatomic) CGFloat chartMarginTop;
@property (nonatomic) CGFloat chartMarginBottom;
/** Controls whether labels should be displayed. */
@property (nonatomic) BOOL showLabel;
/** Controls whether the chart border line should be displayed. */
@property (nonatomic) BOOL showChartBorder;
@property (nonatomic) UIColor *chartBorderColor;
/** Controls whether the chart Horizontal separator should be displayed. */
@property (nonatomic, assign) BOOL showLevelLine;
/** Chart bottom border, co-linear with the x-axis. */
@property (nonatomic) CAShapeLayer * chartBottomLine;
/** Chart bottom border, level separator-linear with the x-axis. */
@property (nonatomic) CAShapeLayer * chartLevelLine;
/** Chart left border, co-linear with the y-axis. */
@property (nonatomic) CAShapeLayer * chartLeftLine;
/** Corner radius for all bars in the chart. */
@property (nonatomic) CGFloat barRadius;
/** Width of all bars in the chart. */
@property (nonatomic) CGFloat barWidth;
@property (nonatomic) CGFloat labelMarginTop;
/** Background color of all bars in the chart. */
@property (nonatomic) UIColor * barBackgroundColor;
/** Text color for all bars in the chart. */
@property (nonatomic) UIColor * labelTextColor;
/** Font for all bars in the chart. */
@property (nonatomic) UIFont * labelFont;
/** How many labels on the x-axis to skip in between displaying labels. */
@property (nonatomic) NSInteger xLabelSkip;
/** How many labels on the y-axis to skip in between displaying labels. */
@property (nonatomic) NSInteger yLabelSum;
/** The maximum for the range of values to display on the y-axis. */
@property (nonatomic) CGFloat yMaxValue;
/** The minimum for the range of values to display on the y-axis. */
@property (nonatomic) CGFloat yMinValue;
/** Controls whether each bar should have a gradient fill. */
@property (nonatomic) UIColor *barColorGradientStart;
/** Controls whether text for x-axis be straight or rotate 45 degree. */
@property (nonatomic) BOOL rotateForXAxisText;
@property (nonatomic, weak) id<PNChartDelegate> delegate;
/**whether show gradient bar*/
@property (nonatomic, assign) BOOL isGradientShow;
/** whether show numbers*/
@property (nonatomic, assign) BOOL isShowNumbers;
@end
+459
View File
@@ -0,0 +1,459 @@
//
// 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
+35
View File
@@ -0,0 +1,35 @@
//
// PNChart.h
// PNChart
//
// Created by Everton Cunha on 06/03/18.
// Copyright © 2018 kevinzhow. All rights reserved.
//
#import <UIKit/UIKit.h>
//! Project version number for PNChart.
FOUNDATION_EXPORT double PNChartVersionNumber;
//! Project version string for PNChart.
FOUNDATION_EXPORT const unsigned char PNChartVersionString[];
#import "PNScatterChart.h"
#import "PNScatterChartData.h"
#import "PNScatterChartDataItem.h"
#import "PNChart.h"
#import "PNChartDelegate.h"
#import "PNChartLabel.h"
#import "PNColor.h"
#import "PNBar.h"
#import "PNBarChart.h"
#import "PNCircleChart.h"
#import "PNLineChart.h"
#import "PNLineChartData.h"
#import "PNLineChartDataItem.h"
#import "PNPieChart.h"
#import "PNPieChartDataItem.h"
#import "PNGenericChart.h"
#import "PNRadarChart.h"
#import "PNRadarChartDataItem.h"
#import "PNChart.h"
+33
View File
@@ -0,0 +1,33 @@
//
// PNChartDelegate.h
// PNChartDemo
//
// Created by kevinzhow on 13-12-11.
// Copyright (c) 2013年 kevinzhow. All rights reserved.
//
#import <Foundation/Foundation.h>
@protocol PNChartDelegate <NSObject>
@optional
/**
* Callback method that gets invoked when the user taps on the chart line.
*/
- (void)userClickedOnLinePoint:(CGPoint)point lineIndex:(NSInteger)lineIndex;
/**
* Callback method that gets invoked when the user taps on a chart line key point.
*/
- (void)userClickedOnLineKeyPoint:(CGPoint)point
lineIndex:(NSInteger)lineIndex
pointIndex:(NSInteger)pointIndex;
/**
* Callback method that gets invoked when the user taps on a chart bar.
*/
- (void)userClickedOnBarAtIndex:(NSInteger)barIndex;
- (void)userClickedOnPieIndexItem:(NSInteger)pieIndex;
- (void)didUnselectPieItem;
@end
+13
View File
@@ -0,0 +1,13 @@
//
// PNChartLabel.h
// PNChart
//
// Created by kevin on 10/3/13.
// Copyright (c) 2013年 kevinzhow. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface PNChartLabel : UILabel
@end
+32
View File
@@ -0,0 +1,32 @@
//
// PNChartLabel.m
// PNChart
//
// Created by kevin on 10/3/13.
// Copyright (c) 2013年 kevinzhow. All rights reserved.
//
#import "PNChartLabel.h"
@implementation PNChartLabel
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.font = [UIFont boldSystemFontOfSize:11.0f];
self.backgroundColor = [UIColor clearColor];
self.textAlignment = NSTextAlignmentCenter;
self.userInteractionEnabled = YES;
self.adjustsFontSizeToFitWidth = YES;
self.numberOfLines = 0;
/* if you want to see ... in large labels un-comment this line
self.minimumScaleFactor = 0.8;
*/
}
return self;
}
@end
+74
View File
@@ -0,0 +1,74 @@
//
// PNCircleChart.h
// PNChartDemo
//
// Created by kevinzhow on 13-11-30.
// Copyright (c) 2013年 kevinzhow. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "PNColor.h"
#import "UICountingLabel.h"
typedef NS_ENUM (NSUInteger, PNChartFormatType) {
PNChartFormatTypePercent,
PNChartFormatTypeDollar,
PNChartFormatTypeNone,
PNChartFormatTypeDecimal,
PNChartFormatTypeDecimalTwoPlaces,
};
#define DEGREES_TO_RADIANS(angle) ((angle) / 180.0 * M_PI)
@interface PNCircleChart : UIView
- (void)strokeChart;
- (void)growChartByAmount:(NSNumber *)growAmount;
- (void)updateChartByCurrent:(NSNumber *)current;
- (void)updateChartByCurrent:(NSNumber *)current byTotal:(NSNumber *)total;
- (id)initWithFrame:(CGRect)frame
total:(NSNumber *)total
current:(NSNumber *)current
clockwise:(BOOL)clockwise;
- (id)initWithFrame:(CGRect)frame
total:(NSNumber *)total
current:(NSNumber *)current
clockwise:(BOOL)clockwise
shadow:(BOOL)hasBackgroundShadow
shadowColor:(UIColor *)backgroundShadowColor;
- (id)initWithFrame:(CGRect)frame
total:(NSNumber *)total
current:(NSNumber *)current
clockwise:(BOOL)clockwise
shadow:(BOOL)hasBackgroundShadow
shadowColor:(UIColor *)backgroundShadowColor
displayCountingLabel:(BOOL)displayCountingLabel;
- (id)initWithFrame:(CGRect)frame
total:(NSNumber *)total
current:(NSNumber *)current
clockwise:(BOOL)clockwise
shadow:(BOOL)hasBackgroundShadow
shadowColor:(UIColor *)backgroundShadowColor
displayCountingLabel:(BOOL)displayCountingLabel
overrideLineWidth:(NSNumber *)overrideLineWidth;
@property (strong, nonatomic) UICountingLabel *countingLabel;
@property (nonatomic) UIColor *strokeColor;
@property (nonatomic) UIColor *strokeColorGradientStart;
@property (nonatomic) NSNumber *total;
@property (nonatomic) NSNumber *current;
@property (nonatomic) NSNumber *lineWidth;
@property (nonatomic) NSTimeInterval duration;
@property (nonatomic) PNChartFormatType chartType;
@property (nonatomic) CAShapeLayer *circle;
@property (nonatomic) CAShapeLayer *gradientMask;
@property (nonatomic) CAShapeLayer *circleBackground;
@property (nonatomic) BOOL displayCountingLabel;
@property (nonatomic) BOOL displayAnimated;
@end
+268
View File
@@ -0,0 +1,268 @@
//
// PNCircleChart.m
// PNChartDemo
//
// Created by kevinzhow on 13-11-30.
// Copyright (c) 2013年 kevinzhow. All rights reserved.
//
#import "PNCircleChart.h"
@implementation PNCircleChart
- (id)initWithFrame:(CGRect)frame total:(NSNumber *)total current:(NSNumber *)current clockwise:(BOOL)clockwise {
return [self initWithFrame:frame
total:total
current:current
clockwise:clockwise
shadow:NO
shadowColor:[UIColor clearColor]
displayCountingLabel:YES
overrideLineWidth:@8.0f];
}
- (id)initWithFrame:(CGRect)frame total:(NSNumber *)total current:(NSNumber *)current clockwise:(BOOL)clockwise shadow:(BOOL)hasBackgroundShadow shadowColor:(UIColor *)backgroundShadowColor {
return [self initWithFrame:frame
total:total
current:current
clockwise:clockwise
shadow:shadow
shadowColor:backgroundShadowColor
displayCountingLabel:YES
overrideLineWidth:@8.0f];
}
- (id)initWithFrame:(CGRect)frame total:(NSNumber *)total current:(NSNumber *)current clockwise:(BOOL)clockwise shadow:(BOOL)hasBackgroundShadow shadowColor:(UIColor *)backgroundShadowColor displayCountingLabel:(BOOL)displayCountingLabel {
return [self initWithFrame:frame
total:total
current:current
clockwise:clockwise
shadow:shadow
shadowColor:backgroundShadowColor
displayCountingLabel:displayCountingLabel
overrideLineWidth:@8.0f];
}
- (id)initWithFrame:(CGRect)frame
total:(NSNumber *)total
current:(NSNumber *)current
clockwise:(BOOL)clockwise
shadow:(BOOL)hasBackgroundShadow
shadowColor:(UIColor *)backgroundShadowColor
displayCountingLabel:(BOOL)displayCountingLabel
overrideLineWidth:(NSNumber *)overrideLineWidth
{
self = [super initWithFrame:frame];
if (self) {
_total = total;
_current = current;
_strokeColor = PNFreshGreen;
_duration = 1.0;
_chartType = PNChartFormatTypePercent;
_displayAnimated = YES;
_displayCountingLabel = displayCountingLabel;
CGFloat startAngle = clockwise ? -90.0f : 270.0f;
CGFloat endAngle = clockwise ? -90.01f : 270.01f;
_lineWidth = overrideLineWidth;
UIBezierPath *circlePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.frame.size.width/2.0f, self.frame.size.height/2.0f)
radius:(self.frame.size.height * 0.5) - ([_lineWidth floatValue]/2.0f)
startAngle:DEGREES_TO_RADIANS(startAngle)
endAngle:DEGREES_TO_RADIANS(endAngle)
clockwise:clockwise];
_circle = [CAShapeLayer layer];
_circle.path = circlePath.CGPath;
_circle.lineCap = kCALineCapRound;
_circle.fillColor = [UIColor clearColor].CGColor;
_circle.lineWidth = [_lineWidth floatValue];
_circle.zPosition = 1;
_circleBackground = [CAShapeLayer layer];
_circleBackground.path = circlePath.CGPath;
_circleBackground.lineCap = kCALineCapRound;
_circleBackground.fillColor = [UIColor clearColor].CGColor;
_circleBackground.lineWidth = [_lineWidth floatValue];
_circleBackground.strokeColor = (hasBackgroundShadow ? backgroundShadowColor.CGColor : [UIColor clearColor].CGColor);
_circleBackground.strokeEnd = 1.0;
_circleBackground.zPosition = -1;
[self.layer addSublayer:_circle];
[self.layer addSublayer:_circleBackground];
_countingLabel = [[UICountingLabel alloc] initWithFrame:CGRectMake(0, 0, 100.0, 50.0)];
[_countingLabel setTextAlignment:NSTextAlignmentCenter];
[_countingLabel setFont:[UIFont boldSystemFontOfSize:16.0f]];
[_countingLabel setTextColor:[UIColor grayColor]];
[_countingLabel setBackgroundColor:[UIColor clearColor]];
[_countingLabel setCenter:CGPointMake(self.frame.size.width/2.0f, self.frame.size.height/2.0f)];
_countingLabel.method = UILabelCountingMethodEaseInOut;
if (_displayCountingLabel) {
[self addSubview:_countingLabel];
}
}
return self;
}
- (void)strokeChart
{
// Add counting label
if (_displayCountingLabel) {
NSString *format;
switch (self.chartType) {
case PNChartFormatTypePercent:
format = @"%d%%";
break;
case PNChartFormatTypeDollar:
format = @"$%d";
break;
case PNChartFormatTypeDecimal:
format = @"%.1f";
break;
case PNChartFormatTypeDecimalTwoPlaces:
format = @"%.2f";
break;
case PNChartFormatTypeNone:
default:
format = @"%d";
break;
}
self.countingLabel.format = format;
[self addSubview:self.countingLabel];
}
// Add circle params
_circle.lineWidth = [_lineWidth floatValue];
_circleBackground.lineWidth = [_lineWidth floatValue];
_circleBackground.strokeEnd = 1.0;
_circle.strokeColor = _strokeColor.CGColor;
_circle.strokeEnd = [_current floatValue] / [_total floatValue];
// Check if user wants to add a gradient from the start color to the bar color
if (_strokeColorGradientStart) {
// Add gradient
self.gradientMask = [CAShapeLayer layer];
self.gradientMask.fillColor = [[UIColor clearColor] CGColor];
self.gradientMask.strokeColor = [[UIColor blackColor] CGColor];
self.gradientMask.lineWidth = _circle.lineWidth;
self.gradientMask.lineCap = kCALineCapRound;
CGRect gradientFrame = CGRectMake(0, 0, 2*self.bounds.size.width, 2*self.bounds.size.height);
self.gradientMask.frame = gradientFrame;
self.gradientMask.path = _circle.path;
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.startPoint = CGPointMake(0.5,1.0);
gradientLayer.endPoint = CGPointMake(0.5,0.0);
gradientLayer.frame = gradientFrame;
UIColor *endColor = (_strokeColor ? _strokeColor : [UIColor greenColor]);
NSArray *colors = @[
(id)endColor.CGColor,
(id)_strokeColorGradientStart.CGColor
];
gradientLayer.colors = colors;
[gradientLayer setMask:self.gradientMask];
[_circle addSublayer:gradientLayer];
self.gradientMask.strokeEnd = [_current floatValue] / [_total floatValue];
}
[self addAnimationIfNeeded];
}
- (void)growChartByAmount:(NSNumber *)growAmount
{
NSNumber *updatedValue = [NSNumber numberWithFloat:[_current floatValue] + [growAmount floatValue]];
// Add animation
[self updateChartByCurrent:updatedValue];
}
-(void)updateChartByCurrent:(NSNumber *)current{
[self updateChartByCurrent:current
byTotal:_total];
}
-(void)updateChartByCurrent:(NSNumber *)current byTotal:(NSNumber *)total {
double totalPercentageValue = [current floatValue]/([total floatValue]/100.0);
if (_strokeColorGradientStart) {
self.gradientMask.strokeEnd = _circle.strokeEnd;
}
// Add animation
if (self.displayAnimated) {
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
pathAnimation.duration = self.duration;
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
pathAnimation.fromValue = @([_current floatValue] / [_total floatValue]);
pathAnimation.toValue = @([current floatValue] / [total floatValue]);
if (_strokeColorGradientStart) {
[self.gradientMask addAnimation:pathAnimation forKey:@"strokeEndAnimation"];
}
[_circle addAnimation:pathAnimation forKey:@"strokeEndAnimation"];
if (_displayCountingLabel) {
[self.countingLabel countFrom:fmin([_current floatValue], [_total floatValue]) to:totalPercentageValue withDuration:self.duration];
}
}
else if (_displayCountingLabel) {
[self.countingLabel countFrom:totalPercentageValue to:totalPercentageValue withDuration:self.duration];
}
_circle.strokeEnd = [current floatValue] / [total floatValue];
_current = current;
_total = total;
}
- (void)addAnimationIfNeeded
{
double percentageValue = (_current.floatValue / _total.floatValue) * 100.0f;
if (self.displayAnimated) {
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
pathAnimation.duration = self.duration;
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
pathAnimation.fromValue = @(0.0f);
pathAnimation.toValue = @([_current floatValue] / [_total floatValue]);
[_circle addAnimation:pathAnimation forKey:@"strokeEndAnimation"];
if(_displayCountingLabel)
{
[_countingLabel countFrom:0 to:percentageValue withDuration:self.duration];
}
if (self.gradientMask && _strokeColorGradientStart) {
[self.gradientMask addAnimation:pathAnimation forKey:@"strokeEndAnimation"];
}
}
else {
if (_displayCountingLabel) {
[_countingLabel countFrom:percentageValue to:percentageValue withDuration:self.duration];
}
}
}
@end
+53
View File
@@ -0,0 +1,53 @@
//
// PNColor.h
// PNChart
//
// Created by kevin on 13-6-8.
// Copyright (c) 2013年 kevinzhow. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
/*
* System Versioning Preprocessor Macros
*/
#define SCREEN_WIDTH ([UIScreen mainScreen].bounds.size.width)
#define PNGrey [UIColor colorWithRed:246.0 / 255.0 green:246.0 / 255.0 blue:246.0 / 255.0 alpha:1.0f]
#define PNLightBlue [UIColor colorWithRed:94.0 / 255.0 green:147.0 / 255.0 blue:196.0 / 255.0 alpha:1.0f]
#define PNGreen [UIColor colorWithRed:77.0 / 255.0 green:186.0 / 255.0 blue:122.0 / 255.0 alpha:1.0f]
#define PNTitleColor [UIColor colorWithRed:0.0 / 255.0 green:189.0 / 255.0 blue:113.0 / 255.0 alpha:1.0f]
#define PNButtonGrey [UIColor colorWithRed:141.0 / 255.0 green:141.0 / 255.0 blue:141.0 / 255.0 alpha:1.0f]
#define PNLightGreen [UIColor colorWithRed:77.0 / 255.0 green:216.0 / 255.0 blue:122.0 / 255.0 alpha:1.0f]
#define PNFreshGreen [UIColor colorWithRed:77.0 / 255.0 green:196.0 / 255.0 blue:122.0 / 255.0 alpha:1.0f]
#define PNDeepGreen [UIColor colorWithRed:77.0 / 255.0 green:176.0 / 255.0 blue:122.0 / 255.0 alpha:1.0f]
#define PNRed [UIColor colorWithRed:245.0 / 255.0 green:94.0 / 255.0 blue:78.0 / 255.0 alpha:1.0f]
#define PNMauve [UIColor colorWithRed:88.0 / 255.0 green:75.0 / 255.0 blue:103.0 / 255.0 alpha:1.0f]
#define PNBrown [UIColor colorWithRed:119.0 / 255.0 green:107.0 / 255.0 blue:95.0 / 255.0 alpha:1.0f]
#define PNBlue [UIColor colorWithRed:82.0 / 255.0 green:116.0 / 255.0 blue:188.0 / 255.0 alpha:1.0f]
#define PNDarkBlue [UIColor colorWithRed:121.0 / 255.0 green:134.0 / 255.0 blue:142.0 / 255.0 alpha:1.0f]
#define PNYellow [UIColor colorWithRed:242.0 / 255.0 green:197.0 / 255.0 blue:117.0 / 255.0 alpha:1.0f]
#define PNWhite [UIColor colorWithRed:255.0 / 255.0 green:255.0 / 255.0 blue:255.0 / 255.0 alpha:1.0f]
#define PNDeepGrey [UIColor colorWithRed:99.0 / 255.0 green:99.0 / 255.0 blue:99.0 / 255.0 alpha:1.0f]
#define PNPinkGrey [UIColor colorWithRed:200.0 / 255.0 green:193.0 / 255.0 blue:193.0 / 255.0 alpha:1.0f]
#define PNHealYellow [UIColor colorWithRed:245.0 / 255.0 green:242.0 / 255.0 blue:238.0 / 255.0 alpha:1.0f]
#define PNLightGrey [UIColor colorWithRed:225.0 / 255.0 green:225.0 / 255.0 blue:225.0 / 255.0 alpha:1.0f]
#define PNCleanGrey [UIColor colorWithRed:251.0 / 255.0 green:251.0 / 255.0 blue:251.0 / 255.0 alpha:1.0f]
#define PNLightYellow [UIColor colorWithRed:241.0 / 255.0 green:240.0 / 255.0 blue:240.0 / 255.0 alpha:1.0f]
#define PNDarkYellow [UIColor colorWithRed:152.0 / 255.0 green:150.0 / 255.0 blue:159.0 / 255.0 alpha:1.0f]
#define PNPinkDark [UIColor colorWithRed:170.0 / 255.0 green:165.0 / 255.0 blue:165.0 / 255.0 alpha:1.0f]
#define PNCloudWhite [UIColor colorWithRed:244.0 / 255.0 green:244.0 / 255.0 blue:244.0 / 255.0 alpha:1.0f]
#define PNBlack [UIColor colorWithRed:45.0 / 255.0 green:45.0 / 255.0 blue:45.0 / 255.0 alpha:1.0f]
#define PNStarYellow [UIColor colorWithRed:252.0 / 255.0 green:223.0 / 255.0 blue:101.0 / 255.0 alpha:1.0f]
#define PNTwitterColor [UIColor colorWithRed:0.0 / 255.0 green:171.0 / 255.0 blue:243.0 / 255.0 alpha:1.0]
#define PNWeiboColor [UIColor colorWithRed:250.0 / 255.0 green:0.0 / 255.0 blue:33.0 / 255.0 alpha:1.0]
#define PNiOSGreenColor [UIColor colorWithRed:98.0 / 255.0 green:247.0 / 255.0 blue:77.0 / 255.0 alpha:1.0]
@interface PNColor : NSObject
- (UIImage *)imageFromColor:(UIColor *)color;
@end
+29
View File
@@ -0,0 +1,29 @@
//
// PNColor.m
// PNChart
//
// Created by kevin on 13-6-8.
// Copyright (c) 2013年 kevinzhow. All rights reserved.
//
#import "PNColor.h"
#import <UIKit/UIKit.h>
@implementation PNColor
- (UIImage *)imageFromColor:(UIColor *)color
{
CGRect rect = CGRectMake(0, 0, 1, 1);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [color CGColor]);
CGContextFillRect(context, rect);
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
@end
+48
View File
@@ -0,0 +1,48 @@
//
// PNGenericChart.h
// PNChartDemo
//
// Created by Andi Palo on 26/02/15.
// Copyright (c) 2015 kevinzhow. All rights reserved.
//
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSUInteger, PNLegendPosition) {
PNLegendPositionTop = 0,
PNLegendPositionBottom = 1,
PNLegendPositionLeft = 2,
PNLegendPositionRight = 3
};
typedef NS_ENUM(NSUInteger, PNLegendItemStyle) {
PNLegendItemStyleStacked = 0,
PNLegendItemStyleSerial = 1
};
@interface PNGenericChart : UIView
@property (assign, nonatomic) BOOL hasLegend;
@property (assign, nonatomic) PNLegendPosition legendPosition;
@property (assign, nonatomic) PNLegendItemStyle legendStyle;
@property (assign, nonatomic) UIFont *legendFont;
@property (assign, nonatomic) UIColor *legendFontColor;
@property (assign, nonatomic) NSUInteger labelRowsInSerialMode;
/** Display the chart with or without animation. Default is YES. **/
@property (nonatomic) BOOL displayAnimated;
/**
* returns the Legend View, or nil if no chart data is present.
* The origin of the legend frame is 0,0 but you can set it with setFrame:(CGRect)
*
* @param mWidth Maximum width of legend. Height will depend on this and font size
*
* @return UIView of Legend
*/
- (UIView*) getLegendWithMaxWidth:(CGFloat)mWidth;
- (void) setupDefaultValues;
@end
+54
View File
@@ -0,0 +1,54 @@
//
// PNGenericChart.m
// PNChartDemo
//
// Created by Andi Palo on 26/02/15.
// Copyright (c) 2015 kevinzhow. All rights reserved.
//
#import "PNGenericChart.h"
@interface PNGenericChart ()
@end
@implementation PNGenericChart
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
}
*/
- (void) setupDefaultValues{
self.hasLegend = YES;
self.legendPosition = PNLegendPositionBottom;
self.legendStyle = PNLegendItemStyleStacked;
self.labelRowsInSerialMode = 1;
self.displayAnimated = YES;
}
/**
* to be implemented in subclass
*/
- (UIView*) getLegendWithMaxWidth:(CGFloat)mWidth{
[self doesNotRecognizeSelector:_cmd];
return nil;
}
- (void) setLabelRowsInSerialMode:(NSUInteger)num{
if (self.legendStyle == PNLegendItemStyleSerial) {
_labelRowsInSerialMode = num;
}else{
_labelRowsInSerialMode = 1;
}
}
@end
+110
View File
@@ -0,0 +1,110 @@
//
// PNLineChart.h
// PNChartDemo
//
// Created by kevin on 11/7/13.
// Copyright (c) 2013年 kevinzhow. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import "PNChartDelegate.h"
#import "PNGenericChart.h"
@interface PNLineChart : PNGenericChart
/**
* Draws the chart in an animated fashion.
*/
- (void)strokeChart;
@property (nonatomic, weak) id<PNChartDelegate> delegate;
@property (nonatomic) NSArray *xLabels;
@property (nonatomic) NSArray *yLabels;
/**
* Array of `LineChartData` objects, one for each line.
*/
@property (nonatomic) NSArray *chartData;
@property (nonatomic) NSMutableArray *pathPoints;
@property (nonatomic) NSMutableArray *xChartLabels;
@property (nonatomic) NSMutableArray *yChartLabels;
@property (nonatomic) CGFloat xLabelWidth;
@property (nonatomic) UIFont *xLabelFont;
@property (nonatomic) UIColor *xLabelColor;
@property (nonatomic) CGFloat yValueMax;
@property (nonatomic) CGFloat yFixedValueMax;
@property (nonatomic) CGFloat yFixedValueMin;
@property (nonatomic) CGFloat yValueMin;
@property (nonatomic) NSInteger yLabelNum;
@property (nonatomic) CGFloat yLabelHeight;
@property (nonatomic) UIFont *yLabelFont;
@property (nonatomic) UIColor *yLabelColor;
@property (nonatomic) CGFloat chartCavanHeight;
@property (nonatomic) CGFloat chartCavanWidth;
@property (nonatomic) BOOL showLabel;
@property (nonatomic) BOOL showGenYLabels;
@property (nonatomic) BOOL showYGridLines;
@property (nonatomic) UIColor *yGridLinesColor;
@property (nonatomic) BOOL thousandsSeparator;
@property (nonatomic) CGFloat chartMarginLeft;
@property (nonatomic) CGFloat chartMarginRight;
@property (nonatomic) CGFloat chartMarginTop;
@property (nonatomic) CGFloat chartMarginBottom;
/**
* Controls whether to show the coordinate axis. Default is NO.
*/
@property (nonatomic, getter = isShowCoordinateAxis) BOOL showCoordinateAxis;
@property (nonatomic) UIColor *axisColor;
@property (nonatomic) CGFloat axisWidth;
@property (nonatomic, strong) NSString *xUnit;
@property (nonatomic, strong) NSString *yUnit;
/**
* String formatter for float values in y-axis labels. If not set, defaults to @"%1.f"
*/
@property (nonatomic, strong) NSString *yLabelFormat;
/**
* Block formatter for custom string in y-axis labels. If not set, defaults to yLabelFormat
*/
@property (nonatomic, copy) NSString* (^yLabelBlockFormatter)(CGFloat);
/**
* Controls whether to curve the line chart or not
*/
@property (nonatomic) BOOL showSmoothLines;
- (void)setXLabels:(NSArray *)xLabels withWidth:(CGFloat)width;
/**
* Update Chart Value
*/
- (void)updateChartData:(NSArray *)data;
/**
* returns the Legend View, or nil if no chart data is present.
* The origin of the legend frame is 0,0 but you can set it with setFrame:(CGRect)
*
* @param mWidth Maximum width of legend. Height will depend on this and font size
*
* @return UIView of Legend
*/
- (UIView*) getLegendWithMaxWidth:(CGFloat)mWidth;
+ (CGSize)sizeOfString:(NSString *)text withWidth:(float)width font:(UIFont *)font;
+ (CGPoint)midPointBetweenPoint1:(CGPoint)point1 andPoint2:(CGPoint)point2;
+ (CGPoint)controlPointBetweenPoint1:(CGPoint)point1 andPoint2:(CGPoint)point2;
@end
File diff suppressed because it is too large Load Diff
+62
View File
@@ -0,0 +1,62 @@
//
// Created by Jörg Polakowski on 14/12/13.
// Copyright (c) 2013 kevinzhow. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSUInteger, PNLineChartPointStyle) {
PNLineChartPointStyleNone = 0,
PNLineChartPointStyleCircle = 1,
PNLineChartPointStyleSquare = 3,
PNLineChartPointStyleTriangle = 4
};
@class PNLineChartDataItem;
typedef PNLineChartDataItem *(^LCLineChartDataGetter)(NSUInteger item);
@interface PNLineChartColorRange : NSObject<NSCopying>
@property(nonatomic) NSRange range;
@property(nonatomic) BOOL inclusive;
@property(nonatomic, retain) UIColor *color;
- (id)initWithRange:(NSRange)range color:(UIColor *)color;
- (id)initWithRange:(NSRange)range color:(UIColor *)color inclusive:(BOOL)isInclusive;
@end
@interface PNLineChartData : NSObject
@property (strong) UIColor *color;
@property (nonatomic) CGFloat alpha;
@property NSUInteger itemCount;
@property (copy) LCLineChartDataGetter getData;
@property (strong, nonatomic) NSString *dataTitle;
@property (nonatomic) BOOL showPointLabel;
@property (nonatomic) UIColor *pointLabelColor;
@property (nonatomic) UIFont *pointLabelFont;
@property (nonatomic) NSString *pointLabelFormat;
@property (nonatomic, assign) PNLineChartPointStyle inflexionPointStyle;
@property (nonatomic) UIColor *inflexionPointColor;
/**
* if rangeColor is set and the lineChartData values are within any
* of the given range then use the rangeColor.color otherwise use
* self.color for the rest of the graph
*/
@property(strong) NSArray<PNLineChartColorRange *> *rangeColors;
/**
* If PNLineChartPointStyle is circle, this returns the circle's diameter.
* If PNLineChartPointStyle is square, each point is a square with each side equal in length to this value.
*/
@property (nonatomic, assign) CGFloat inflexionPointWidth;
@property (nonatomic, assign) CGFloat lineWidth;
@end
+66
View File
@@ -0,0 +1,66 @@
//
// Created by Jörg Polakowski on 14/12/13.
// Copyright (c) 2013 kevinzhow. All rights reserved.
//
#import "PNLineChartData.h"
@implementation PNLineChartColorRange
- (id)initWithRange:(NSRange)range color:(UIColor *)color {
self = [super init];
if (self) {
self.range = range;
self.color = color;
self.inclusive = NO;
}
return self;
}
- (id)initWithRange:(NSRange)range color:(UIColor *)color inclusive:(BOOL)isInclusive {
self = [super init];
if (self) {
self.range = range;
self.color = color;
self.inclusive = isInclusive;
}
return self;
}
- (id)copyWithZone:(NSZone *)zone {
PNLineChartColorRange *copy = [[self class] allocWithZone:zone];
copy.color = self.color;
copy.range = self.range;
return copy;
}
@end
@implementation PNLineChartData
- (id)init
{
self = [super init];
if (self) {
[self setupDefaultValues];
}
return self;
}
- (void)setupDefaultValues
{
_inflexionPointStyle = PNLineChartPointStyleNone;
_inflexionPointWidth = 6.f;
_lineWidth = 2.f;
_alpha = 1.f;
_showPointLabel = NO;
_pointLabelColor = [UIColor blackColor];
_pointLabelFormat = @"%1.f";
_rangeColors = nil;
}
@end
+17
View File
@@ -0,0 +1,17 @@
//
// Created by Jörg Polakowski on 14/12/13.
// Copyright (c) 2013 kevinzhow. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface PNLineChartDataItem : NSObject
+ (PNLineChartDataItem *)dataItemWithY:(CGFloat)y;
+ (PNLineChartDataItem *)dataItemWithY:(CGFloat)y andRawY:(CGFloat)rawY;
@property (readonly) CGFloat y; // should be within the y range
@property (readonly) CGFloat rawY; // this is the raw value, used for point label.
@end
+38
View File
@@ -0,0 +1,38 @@
//
// Created by Jörg Polakowski on 14/12/13.
// Copyright (c) 2013 kevinzhow. All rights reserved.
//
#import "PNLineChartDataItem.h"
@interface PNLineChartDataItem ()
- (id)initWithY:(CGFloat)y andRawY:(CGFloat)rawY;
@property (readwrite) CGFloat y; // should be within the y range
@property (readwrite) CGFloat rawY; // this is the raw value, used for point label.
@end
@implementation PNLineChartDataItem
+ (PNLineChartDataItem *)dataItemWithY:(CGFloat)y
{
return [[PNLineChartDataItem alloc] initWithY:y andRawY:y];
}
+ (PNLineChartDataItem *)dataItemWithY:(CGFloat)y andRawY:(CGFloat)rawY {
return [[PNLineChartDataItem alloc] initWithY:y andRawY:rawY];
}
- (id)initWithY:(CGFloat)y andRawY:(CGFloat)rawY
{
if ((self = [super init])) {
self.y = y;
self.rawY = rawY;
}
return self;
}
@end
+72
View File
@@ -0,0 +1,72 @@
//
// PNPieChart.h
// PNChartDemo
//
// Created by Hang Zhang on 14-5-5.
// Copyright (c) 2014年 kevinzhow. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "PNPieChartDataItem.h"
#import "PNGenericChart.h"
#import "PNChartDelegate.h"
#if __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_10_0
@interface PNPieChart : PNGenericChart
#else
@interface PNPieChart : PNGenericChart <CAAnimationDelegate>
#endif
- (id)initWithFrame:(CGRect)frame items:(NSArray *)items;
@property (nonatomic, readonly) NSArray *items;
/** Default is 18-point Avenir Medium. */
@property (nonatomic) UIFont *descriptionTextFont;
/** Default is white. */
@property (nonatomic) UIColor *descriptionTextColor;
/** Default is black, with an alpha of 0.4. */
@property (nonatomic) UIColor *descriptionTextShadowColor;
/** Default is CGSizeMake(0, 1). */
@property (nonatomic) CGSize descriptionTextShadowOffset;
/** Default is 1.0. */
@property (nonatomic) NSTimeInterval duration;
/** Show only values, this is useful when legend is present */
@property (nonatomic) BOOL showOnlyValues;
/** Show absolute values not relative i.e. percentages */
@property (nonatomic) BOOL showAbsoluteValues;
/** Hide percentage labels less than cutoff value */
@property (nonatomic, assign) CGFloat labelPercentageCutoff;
/** Default YES. */
@property (nonatomic) BOOL shouldHighlightSectorOnTouch;
/** Current outer radius. Override recompute() to change this. **/
@property (nonatomic) CGFloat outerCircleRadius;
/** Current inner radius. Override recompute() to change this. **/
@property (nonatomic) CGFloat innerCircleRadius;
@property (nonatomic, weak) id<PNChartDelegate> delegate;
/** Update chart items. Does not update chart itself. */
- (void)updateChartData:(NSArray *)data;
/** Multiple selection */
@property (nonatomic, assign) BOOL enableMultipleSelection;
/** show only tiles, not values or percentage */
@property (nonatomic) BOOL hideValues;
- (void)strokeChart;
- (void)recompute;
@end
+511
View File
@@ -0,0 +1,511 @@
//
// PNPieChart.m
// PNChartDemo
//
// Created by Hang Zhang on 14-5-5.
// Copyright (c) 2014年 kevinzhow. All rights reserved.
//
#import "PNPieChart.h"
//needed for the expected label size
#import "PNLineChart.h"
@interface PNPieChart()<CAAnimationDelegate>
@property (nonatomic) NSArray *items;
@property (nonatomic) NSArray *endPercentages;
@property (nonatomic) UIView *contentView;
@property (nonatomic) CAShapeLayer *pieLayer;
@property (nonatomic) NSMutableArray *descriptionLabels;
@property (strong, nonatomic) CAShapeLayer *sectorHighlight;
@property (nonatomic, strong) NSMutableDictionary *selectedItems;
- (void)loadDefault;
- (UILabel *)descriptionLabelForItemAtIndex:(NSUInteger)index;
- (PNPieChartDataItem *)dataItemForIndex:(NSUInteger)index;
- (CGFloat)startPercentageForItemAtIndex:(NSUInteger)index;
- (CGFloat)endPercentageForItemAtIndex:(NSUInteger)index;
- (CGFloat)ratioForItemAtIndex:(NSUInteger)index;
- (CAShapeLayer *)newCircleLayerWithRadius:(CGFloat)radius
borderWidth:(CGFloat)borderWidth
fillColor:(UIColor *)fillColor
borderColor:(UIColor *)borderColor
startPercentage:(CGFloat)startPercentage
endPercentage:(CGFloat)endPercentage;
@end
@implementation PNPieChart
-(id)initWithFrame:(CGRect)frame items:(NSArray *)items{
self = [self initWithFrame:frame];
if(self){
_items = [NSArray arrayWithArray:items];
[self baseInit];
}
return self;
}
- (void)awakeFromNib{
[super awakeFromNib];
[self baseInit];
}
- (void)baseInit{
_selectedItems = [NSMutableDictionary dictionary];
//在绘制圆形时,应当考虑矩形的宽和高的大小问题,当宽大于高时,绘制饼图时,会超出整个view的范围,因此建议在此处进行判断
CGFloat minimal = (CGRectGetWidth(self.bounds) < CGRectGetHeight(self.bounds)) ? CGRectGetWidth(self.bounds) : CGRectGetHeight(self.bounds);
_outerCircleRadius = minimal / 2;
_innerCircleRadius = minimal / 6;
// _outerCircleRadius = CGRectGetWidth(self.bounds) / 2;
// _innerCircleRadius = CGRectGetWidth(self.bounds) / 6;
_descriptionTextColor = [UIColor whiteColor];
_descriptionTextFont = [UIFont fontWithName:@"Avenir-Medium" size:18.0];
_descriptionTextShadowColor = [[UIColor blackColor] colorWithAlphaComponent:0.4];
_descriptionTextShadowOffset = CGSizeMake(0, 1);
_duration = 1.0;
_shouldHighlightSectorOnTouch = YES;
_enableMultipleSelection = NO;
_hideValues = NO;
[super setupDefaultValues];
[self loadDefault];
}
- (void)loadDefault{
__block CGFloat currentTotal = 0;
CGFloat total = [[self.items valueForKeyPath:@"@sum.value"] floatValue];
NSMutableArray *endPercentages = [NSMutableArray new];
[_items enumerateObjectsUsingBlock:^(PNPieChartDataItem *item, NSUInteger idx, BOOL *stop) {
if (total == 0){
[endPercentages addObject:@(1.0 / _items.count * (idx + 1))];
}else{
currentTotal += item.value;
[endPercentages addObject:@(currentTotal / total)];
}
}];
self.endPercentages = [endPercentages copy];
[_contentView removeFromSuperview];
_contentView = [[UIView alloc] initWithFrame:self.bounds];
[self addSubview:_contentView];
_descriptionLabels = [NSMutableArray new];
_pieLayer = [CAShapeLayer layer];
[_contentView.layer addSublayer:_pieLayer];
}
/** Override this to change how inner attributes are computed. **/
- (void)recompute {
//同理
CGFloat minimal = (CGRectGetWidth(self.bounds) < CGRectGetHeight(self.bounds)) ? CGRectGetWidth(self.bounds) : CGRectGetHeight(self.bounds);
self.outerCircleRadius = minimal / 2;
self.innerCircleRadius = minimal / 6;
}
#pragma mark -
- (void)strokeChart{
[self loadDefault];
[self recompute];
PNPieChartDataItem *currentItem;
for (int i = 0; i < _items.count; i++) {
currentItem = [self dataItemForIndex:i];
CGFloat startPercentage = [self startPercentageForItemAtIndex:i];
CGFloat endPercentage = [self endPercentageForItemAtIndex:i];
CGFloat radius = _innerCircleRadius + (_outerCircleRadius - _innerCircleRadius) / 2;
CGFloat borderWidth = _outerCircleRadius - _innerCircleRadius;
CAShapeLayer *currentPieLayer = [self newCircleLayerWithRadius:radius
borderWidth:borderWidth
fillColor:[UIColor clearColor]
borderColor:currentItem.color
startPercentage:startPercentage
endPercentage:endPercentage];
[_pieLayer addSublayer:currentPieLayer];
}
[self maskChart];
for (int i = 0; i < _items.count; i++) {
UILabel *descriptionLabel = [self descriptionLabelForItemAtIndex:i];
[_contentView addSubview:descriptionLabel];
[_descriptionLabels addObject:descriptionLabel];
}
[self addAnimationIfNeeded];
}
- (UILabel *)descriptionLabelForItemAtIndex:(NSUInteger)index{
PNPieChartDataItem *currentDataItem = [self dataItemForIndex:index];
CGFloat distance = _innerCircleRadius + (_outerCircleRadius - _innerCircleRadius) / 2;
CGFloat centerPercentage = ([self startPercentageForItemAtIndex:index] + [self endPercentageForItemAtIndex:index])/ 2;
CGFloat rad = centerPercentage * 2 * M_PI;
UILabel *descriptionLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 80)];
NSString *titleText = currentDataItem.textDescription;
NSString *titleValue;
if (self.showAbsoluteValues) {
titleValue = [NSString stringWithFormat:@"%.0f",currentDataItem.value];
}else{
titleValue = [NSString stringWithFormat:@"%.0f%%",[self ratioForItemAtIndex:index] * 100];
}
if (self.hideValues)
descriptionLabel.text = titleText;
else if(!titleText || self.showOnlyValues)
descriptionLabel.text = titleValue;
else {
NSString* str = [titleValue stringByAppendingString:[NSString stringWithFormat:@"\n%@",titleText]];
descriptionLabel.text = str ;
}
//If value is less than cutoff, show no label
if ([self ratioForItemAtIndex:index] < self.labelPercentageCutoff )
{
descriptionLabel.text = nil;
}
CGPoint center = CGPointMake(_outerCircleRadius + distance * sin(rad),
_outerCircleRadius - distance * cos(rad));
descriptionLabel.font = _descriptionTextFont;
CGSize labelSize = [descriptionLabel.text sizeWithAttributes:@{NSFontAttributeName:descriptionLabel.font}];
descriptionLabel.frame = CGRectMake(descriptionLabel.frame.origin.x, descriptionLabel.frame.origin.y,
descriptionLabel.frame.size.width, labelSize.height);
descriptionLabel.numberOfLines = 0;
descriptionLabel.textColor = _descriptionTextColor;
descriptionLabel.shadowColor = _descriptionTextShadowColor;
descriptionLabel.shadowOffset = _descriptionTextShadowOffset;
descriptionLabel.textAlignment = NSTextAlignmentCenter;
descriptionLabel.center = center;
descriptionLabel.alpha = 0;
descriptionLabel.backgroundColor = [UIColor clearColor];
return descriptionLabel;
}
- (void)updateChartData:(NSArray *)items {
self.items = items;
}
- (PNPieChartDataItem *)dataItemForIndex:(NSUInteger)index{
return self.items[index];
}
- (CGFloat)startPercentageForItemAtIndex:(NSUInteger)index{
if(index == 0){
return 0;
}
return [_endPercentages[index - 1] floatValue];
}
- (CGFloat)endPercentageForItemAtIndex:(NSUInteger)index{
return [_endPercentages[index] floatValue];
}
- (CGFloat)ratioForItemAtIndex:(NSUInteger)index{
return [self endPercentageForItemAtIndex:index] - [self startPercentageForItemAtIndex:index];
}
#pragma mark private methods
- (CAShapeLayer *)newCircleLayerWithRadius:(CGFloat)radius
borderWidth:(CGFloat)borderWidth
fillColor:(UIColor *)fillColor
borderColor:(UIColor *)borderColor
startPercentage:(CGFloat)startPercentage
endPercentage:(CGFloat)endPercentage{
CAShapeLayer *circle = [CAShapeLayer layer];
CGPoint center = CGPointMake(CGRectGetMidX(self.bounds),CGRectGetMidY(self.bounds));
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center
radius:radius
startAngle:-M_PI_2
endAngle:M_PI_2 * 3
clockwise:YES];
circle.fillColor = fillColor.CGColor;
circle.strokeColor = borderColor.CGColor;
circle.strokeStart = startPercentage;
circle.strokeEnd = endPercentage;
circle.lineWidth = borderWidth;
circle.path = path.CGPath;
return circle;
}
- (void)maskChart{
CGFloat radius = _innerCircleRadius + (_outerCircleRadius - _innerCircleRadius) / 2;
CGFloat borderWidth = _outerCircleRadius - _innerCircleRadius;
CAShapeLayer *maskLayer = [self newCircleLayerWithRadius:radius
borderWidth:borderWidth
fillColor:[UIColor clearColor]
borderColor:[UIColor blackColor]
startPercentage:0
endPercentage:1];
_pieLayer.mask = maskLayer;
}
- (void)addAnimationIfNeeded{
if (self.displayAnimated) {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
animation.duration = _duration;
animation.fromValue = @0;
animation.toValue = @1;
animation.delegate = self;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.removedOnCompletion = YES;
[_pieLayer.mask addAnimation:animation forKey:@"circleAnimation"];
}
else {
// Add description labels since no animation is required
[_descriptionLabels enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[obj setAlpha:1];
}];
}
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
[_descriptionLabels enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[UIView animateWithDuration:0.2 animations:^(){
[obj setAlpha:1];
}];
}];
}
- (void)didTouchAt:(CGPoint)touchLocation
{
CGPoint circleCenter = CGPointMake(_contentView.bounds.size.width/2, _contentView.bounds.size.height/2);
CGFloat distanceFromCenter = sqrtf(powf((touchLocation.y - circleCenter.y),2) + powf((touchLocation.x - circleCenter.x),2));
if (distanceFromCenter < _innerCircleRadius) {
if ([self.delegate respondsToSelector:@selector(didUnselectPieItem)]) {
[self.delegate didUnselectPieItem];
}
[self.sectorHighlight removeFromSuperlayer];
return;
}
CGFloat percentage = [self findPercentageOfAngleInCircle:circleCenter fromPoint:touchLocation];
int index = 0;
while (percentage > [self endPercentageForItemAtIndex:index]) {
index ++;
}
if ([self.delegate respondsToSelector:@selector(userClickedOnPieIndexItem:)]) {
[self.delegate userClickedOnPieIndexItem:index];
}
if (self.shouldHighlightSectorOnTouch)
{
if (!self.enableMultipleSelection)
{
if (self.sectorHighlight)
[self.sectorHighlight removeFromSuperlayer];
}
PNPieChartDataItem *currentItem = [self dataItemForIndex:index];
CGFloat red,green,blue,alpha;
UIColor *old = currentItem.color;
[old getRed:&red green:&green blue:&blue alpha:&alpha];
alpha /= 2;
UIColor *newColor = [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
CGFloat startPercentage = [self startPercentageForItemAtIndex:index];
CGFloat endPercentage = [self endPercentageForItemAtIndex:index];
self.sectorHighlight = [self newCircleLayerWithRadius:_outerCircleRadius + 5
borderWidth:10
fillColor:[UIColor clearColor]
borderColor:newColor
startPercentage:startPercentage
endPercentage:endPercentage];
if (self.enableMultipleSelection)
{
NSString *dictIndex = [NSString stringWithFormat:@"%d", index];
CAShapeLayer *indexShape = [self.selectedItems valueForKey:dictIndex];
if (indexShape)
{
[indexShape removeFromSuperlayer];
[self.selectedItems removeObjectForKey:dictIndex];
}
else
{
[self.selectedItems setObject:self.sectorHighlight forKey:dictIndex];
[_contentView.layer addSublayer:self.sectorHighlight];
}
}
else
{
[_contentView.layer addSublayer:self.sectorHighlight];
}
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches) {
CGPoint touchLocation = [touch locationInView:_contentView];
[self didTouchAt:touchLocation];
}
}
- (CGFloat) findPercentageOfAngleInCircle:(CGPoint)center fromPoint:(CGPoint)reference{
//Find angle of line Passing In Reference And Center
CGFloat angleOfLine = atanf((reference.y - center.y) / (reference.x - center.x));
CGFloat percentage = (angleOfLine + M_PI/2)/(2 * M_PI);
return (reference.x - center.x) > 0 ? percentage : percentage + .5;
}
- (UIView*) getLegendWithMaxWidth:(CGFloat)mWidth{
if ([self.items count] < 1) {
return nil;
}
/* This is a small circle that refers to the chart data */
CGFloat legendCircle = 16;
CGFloat hSpacing = 0;
CGFloat beforeLabel = legendCircle + hSpacing;
/* x and y are the coordinates of the starting point of each legend item */
CGFloat x = 0;
CGFloat y = 0;
/* accumulated width and height */
CGFloat totalWidth = 0;
CGFloat totalHeight = 0;
NSMutableArray *legendViews = [[NSMutableArray alloc] init];
/* Determine the max width of each legend item */
CGFloat maxLabelWidth;
if (self.legendStyle == PNLegendItemStyleStacked) {
maxLabelWidth = mWidth - beforeLabel;
}else{
maxLabelWidth = MAXFLOAT;
}
/* this is used when labels wrap text and the line
* should be in the middle of the first row */
CGFloat singleRowHeight = [PNLineChart sizeOfString:@"Test"
withWidth:MAXFLOAT
font:self.legendFont ? self.legendFont : [UIFont systemFontOfSize:12.0f]].height;
NSUInteger counter = 0;
NSUInteger rowWidth = 0;
NSUInteger rowMaxHeight = 0;
for (PNPieChartDataItem *pdata in self.items) {
if (!pdata.textDescription) {
break;
}
/* Expected label size*/
CGSize labelsize = [PNLineChart sizeOfString:pdata.textDescription
withWidth:maxLabelWidth
font:self.legendFont ? self.legendFont : [UIFont systemFontOfSize:12.0f]];
if ((rowWidth + labelsize.width + beforeLabel > mWidth)&&(self.legendStyle == PNLegendItemStyleSerial)) {
rowWidth = 0;
x = 0;
y += rowMaxHeight;
rowMaxHeight = 0;
}
rowWidth += labelsize.width + beforeLabel;
totalWidth = self.legendStyle == PNLegendItemStyleSerial ? fmaxf(rowWidth, totalWidth) : fmaxf(totalWidth, labelsize.width + beforeLabel);
// Add inflexion type
[legendViews addObject:[self drawInflexion:legendCircle * .6
center:CGPointMake(x + legendCircle / 2, y + singleRowHeight / 2)
andColor:pdata.color]];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(x + beforeLabel, y, labelsize.width, labelsize.height)];
label.text = pdata.textDescription;
label.textColor = self.legendFontColor ? self.legendFontColor : [UIColor blackColor];
label.font = self.legendFont ? self.legendFont : [UIFont systemFontOfSize:12.0f];
label.lineBreakMode = NSLineBreakByWordWrapping;
label.numberOfLines = 0;
rowMaxHeight = fmaxf(rowMaxHeight, labelsize.height);
x += self.legendStyle == PNLegendItemStyleStacked ? 0 : labelsize.width + beforeLabel;
y += self.legendStyle == PNLegendItemStyleStacked ? labelsize.height : 0;
totalHeight = self.legendStyle == PNLegendItemStyleSerial ? fmaxf(totalHeight, rowMaxHeight + y) : totalHeight + labelsize.height;
[legendViews addObject:label];
counter ++;
}
UIView *legend = [[UIView alloc] initWithFrame:CGRectMake(0, 0, totalWidth, totalHeight)];
for (UIView* v in legendViews) {
[legend addSubview:v];
}
return legend;
}
- (UIImageView*)drawInflexion:(CGFloat)size center:(CGPoint)center andColor:(UIColor*)color
{
//Make the size a little bigger so it includes also border stroke
CGSize aSize = CGSizeMake(size, size);
UIGraphicsBeginImageContextWithOptions(aSize, NO, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddArc(context, size/2, size/ 2, size/2, 0, M_PI*2, YES);
//Set some fill color
CGContextSetFillColorWithColor(context, color.CGColor);
//Finally draw
CGContextDrawPath(context, kCGPathFill);
//now get the image from the context
UIImage *squareImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//// Translate origin
CGFloat originX = center.x - (size) / 2.0;
CGFloat originY = center.y - (size) / 2.0;
UIImageView *squareImageView = [[UIImageView alloc]initWithImage:squareImage];
[squareImageView setFrame:CGRectMake(originX, originY, size, size)];
return squareImageView;
}
/* Redraw the chart on autolayout */
-(void)layoutSubviews {
[super layoutSubviews];
[self strokeChart];
}
@end
+25
View File
@@ -0,0 +1,25 @@
//
// PNPieChartDataItem.h
// PNChartDemo
//
// Created by Hang Zhang on 14-5-5.
// Copyright (c) 2014年 kevinzhow. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface PNPieChartDataItem : NSObject
+ (instancetype)dataItemWithValue:(CGFloat)value
color:(UIColor*)color;
+ (instancetype)dataItemWithValue:(CGFloat)value
color:(UIColor*)color
description:(NSString *)description;
@property (nonatomic) CGFloat value;
@property (nonatomic) UIColor *color;
@property (nonatomic) NSString *textDescription;
@end
+38
View File
@@ -0,0 +1,38 @@
//
// PNPieChartDataItem.m
// PNChartDemo
//
// Created by Hang Zhang on 14-5-5.
// Copyright (c) 2014年 kevinzhow. All rights reserved.
//
#import "PNPieChartDataItem.h"
#import <UIKit/UIKit.h>
@implementation PNPieChartDataItem
+ (instancetype)dataItemWithValue:(CGFloat)value
color:(UIColor*)color{
PNPieChartDataItem *item = [PNPieChartDataItem new];
item.value = value;
item.color = color;
return item;
}
+ (instancetype)dataItemWithValue:(CGFloat)value
color:(UIColor*)color
description:(NSString *)description {
PNPieChartDataItem *item = [PNPieChartDataItem dataItemWithValue:value color:color];
item.textDescription = description;
return item;
}
- (void)setValue:(CGFloat)value{
NSAssert(value >= 0, @"value should >= 0");
if (value != _value){
_value = value;
}
}
@end
+53
View File
@@ -0,0 +1,53 @@
//
// PNRadarChart.h
// PNChartDemo
//
// Created by Lei on 15/7/1.
// Copyright (c) 2015年 kevinzhow. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "PNGenericChart.h"
#import "PNRadarChartDataItem.h"
#define MAXCIRCLE 20
typedef NS_ENUM(NSUInteger, PNRadarChartLabelStyle) {
PNRadarChartLabelStyleCircle = 0,
PNRadarChartLabelStyleHorizontal,
PNRadarChartLabelStyleHidden,
};
@interface PNRadarChart : PNGenericChart
-(id)initWithFrame:(CGRect)frame items:(NSArray *)items valueDivider:(CGFloat)unitValue;
/**
*Draws the chart in an animated fashion.
*/
-(void)strokeChart;
/** Array of `RadarChartDataItem` objects, one for each corner. */
@property (nonatomic) NSArray *chartData;
/** The unit of this chart ,default is 1 */
@property (nonatomic) CGFloat valueDivider;
/** The maximum for the range of values to display on the chart */
@property (nonatomic) CGFloat maxValue;
/** Default is gray. */
@property (nonatomic) UIColor *webColor;
/** Default is green , with an alpha of 0.7 */
@property (nonatomic) UIColor *plotColor;
/** Default is black */
@property (nonatomic) UIColor *fontColor;
/** Default is orange */
@property (nonatomic) UIColor *graduationColor;
/** Default is 15 */
@property (nonatomic) CGFloat fontSize;
/** Controls the labels display style that around chart */
@property (nonatomic, assign) PNRadarChartLabelStyle labelStyle;
/** Tap the label will display detail value ,default is YES. */
@property (nonatomic, assign) BOOL isLabelTouchable;
/** is show graduation on the chart ,default is NO. */
@property (nonatomic, assign) BOOL isShowGraduation;
@end
+380
View File
@@ -0,0 +1,380 @@
//
// 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 + M_PI_2 * 3;
[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];
// self.transform = CGAffineTransformMakeRotation(-M_PI_2);
}
#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.textColor = _fontColor;
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 + M_PI_2 * 3);
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;
}
if ((int)x == (int)_centerX) {
label.frame = CGRectMake(x - detailSize.width * 0.5, y - detailSize.height * 0.5, detailSize.width , detailSize.height);
label.textAlignment = NSTextAlignmentCenter;
}
break;
case PNRadarChartLabelStyleHidden:
[label setHidden:YES];
break;
default:
break;
}
[label sizeToFit];
if (_isLabelTouchable) {
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 = _graduationColor;
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
@@ -0,0 +1,19 @@
//
// PNRadarChartDataItem.h
// PNChartDemo
//
// Created by Lei on 15/7/1.
// Copyright (c) 2015年 kevinzhow. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
@interface PNRadarChartDataItem : NSObject
+ (instancetype)dataItemWithValue:(CGFloat)value
description:(NSString *)description;
@property (nonatomic) CGFloat value;
@property (nonatomic,copy) NSString *textDescription;
@end
@@ -0,0 +1,29 @@
//
// PNRadarChartDataItem.m
// PNChartDemo
//
// Created by Lei on 15/7/1.
// Copyright (c) 2015年 kevinzhow. All rights reserved.
//
#import "PNRadarChartDataItem.h"
@implementation PNRadarChartDataItem
+ (instancetype)dataItemWithValue:(CGFloat)value
description:(NSString *)description {
PNRadarChartDataItem *item = [PNRadarChartDataItem new];
item.value = value;
item.textDescription = description;
return item;
}
- (void)setValue:(CGFloat)value {
if (value < 0) {
value = 0;
NSLog(@"Value value can not be negative");
}
_value = value;
}
@end
+69
View File
@@ -0,0 +1,69 @@
//
// PNScatterChart.h
// PNChartDemo
//
// Created by Alireza Arabi on 12/4/14.
// Copyright (c) 2014 kevinzhow. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import "PNChartDelegate.h"
#import "PNGenericChart.h"
#import "PNScatterChartData.h"
#import "PNScatterChartDataItem.h"
@interface PNScatterChart : PNGenericChart
@property (nonatomic, retain) id<PNChartDelegate> delegate;
/** Array of `ScatterChartData` objects, one for each line. */
@property (nonatomic) NSArray *chartData;
/** Controls whether to show the coordinate axis. Default is NO. */
@property (nonatomic, getter = isShowCoordinateAxis) BOOL showCoordinateAxis;
@property (nonatomic) UIColor *axisColor;
@property (nonatomic) CGFloat axisWidth;
/** String formatter for float values in x-axis/y-axis labels. If not set, defaults to @"%1.f" */
@property (nonatomic, strong) NSString *xLabelFormat;
@property (nonatomic, strong) NSString *yLabelFormat;
/** Default is true. */
@property (nonatomic) BOOL showLabel;
/** Default is 18-point Avenir Medium. */
@property (nonatomic) UIFont *descriptionTextFont;
/** Default is white. */
@property (nonatomic) UIColor *descriptionTextColor;
/** Default is black, with an alpha of 0.4. */
@property (nonatomic) UIColor *descriptionTextShadowColor;
/** Default is CGSizeMake(0, 1). */
@property (nonatomic) CGSize descriptionTextShadowOffset;
/** Default is 1.0. */
@property (nonatomic) NSTimeInterval duration;
@property (nonatomic) CGFloat AxisX_minValue;
@property (nonatomic) CGFloat AxisX_maxValue;
@property (nonatomic) CGFloat AxisY_minValue;
@property (nonatomic) CGFloat AxisY_maxValue;
- (void) setAxisXWithMinimumValue:(CGFloat)minVal andMaxValue:(CGFloat)maxVal toTicks:(int)numberOfTicks;
- (void) setAxisYWithMinimumValue:(CGFloat)minVal andMaxValue:(CGFloat)maxVal toTicks:(int)numberOfTicks;
- (void) setAxisXLabel:(NSArray *)array;
- (void) setAxisYLabel:(NSArray *)array;
- (void) setup;
- (void) drawLineFromPoint : (CGPoint) startPoint ToPoint : (CGPoint) endPoint WithLineWith : (CGFloat) lineWidth AndWithColor : (UIColor*) color;
/**
* Update Chart Value
*/
- (void)updateChartData:(NSArray *)data;
@end
+445
View File
@@ -0,0 +1,445 @@
//
// PNScatterChart.m
// PNChartDemo
//
// Created by Alireza Arabi on 12/4/14.
// Copyright (c) 2014 kevinzhow. All rights reserved.
//
#import "PNScatterChart.h"
#import "PNColor.h"
#import "PNChartLabel.h"
#import "PNScatterChartData.h"
#import "PNScatterChartDataItem.h"
@interface PNScatterChart ()
@property (nonatomic, weak) CAShapeLayer *pathLayer;
@property (nonatomic, weak) NSMutableArray *verticalLineLayer;
@property (nonatomic, weak) NSMutableArray *horizentalLinepathLayer;
@property (nonatomic) CGPoint startPoint;
@property (nonatomic) CGPoint startPointVectorX;
@property (nonatomic) CGPoint endPointVecotrX;
@property (nonatomic) CGPoint startPointVectorY;
@property (nonatomic) CGPoint endPointVecotrY;
@property (nonatomic) CGFloat vectorX_Steps;
@property (nonatomic) CGFloat vectorY_Steps;
@property (nonatomic) CGFloat vectorX_Size;
@property (nonatomic) CGFloat vectorY_Size;
@property (nonatomic) NSMutableArray *axisX_labels;
@property (nonatomic) NSMutableArray *axisY_labels;
@property (nonatomic) int AxisX_partNumber ;
@property (nonatomic) int AxisY_partNumber ;
@property (nonatomic) CGFloat AxisX_step ;
@property (nonatomic) CGFloat AxisY_step ;
@property (nonatomic) CGFloat AxisX_Margin;
@property (nonatomic) CGFloat AxisY_Margin;
@property (nonatomic) BOOL isForUpdate;
@end
@implementation PNScatterChart
#pragma mark initialization
- (id)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self setupDefaultValues];
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setupDefaultValues];
}
return self;
}
- (void) setup
{
[self vectorXSetup];
[self vectorYSetup];
}
- (void)setupDefaultValues
{
[super setupDefaultValues];
// Initialization code
self.backgroundColor = [UIColor whiteColor];
self.clipsToBounds = YES;
_showLabel = YES;
_isForUpdate = NO;
self.userInteractionEnabled = YES;
// Coordinate Axis Default Values
_showCoordinateAxis = YES;
_axisColor = [UIColor colorWithRed:0.4f green:0.4f blue:0.4f alpha:1.f];
_axisWidth = 1.f;
// Initialization code
_AxisX_Margin = 30 ;
_AxisY_Margin = 30 ;
// self.frame = CGRectMake((SCREEN_WIDTH - self.frame.size.width) / 2, 200, self.frame.size.width, self.frame.size.height) ;
self.backgroundColor = [UIColor clearColor];
_startPoint.y = self.frame.size.height - self.AxisY_Margin ;
_startPoint.x = self.AxisX_Margin ;
_axisX_labels = [NSMutableArray array];
_axisY_labels = [NSMutableArray array];
_descriptionTextColor = [UIColor blackColor];
_descriptionTextFont = [UIFont fontWithName:@"Avenir-Medium" size:9.0];
_descriptionTextShadowColor = [[UIColor blackColor] colorWithAlphaComponent:0.4];
_descriptionTextShadowOffset = CGSizeMake(0, 1);
_duration = 1.0;
}
#pragma mark calculating axis
- (void) setAxisXWithMinimumValue:(CGFloat)minVal andMaxValue:(CGFloat)maxVal toTicks:(int)numberOfTicks
{
_AxisX_minValue = minVal ;
_AxisX_maxValue = maxVal ;
_AxisX_partNumber = numberOfTicks - 1;
_AxisX_step = (float)((maxVal - minVal)/_AxisX_partNumber);
NSString *LabelFormat = self.xLabelFormat ? : @"%1.f";
CGFloat tempValue = minVal ;
UILabel *label = [[UILabel alloc] init];
label.text = [NSString stringWithFormat:LabelFormat,minVal] ;
[_axisX_labels addObject:label];
for (int i = 0 ; i < _AxisX_partNumber; i++) {
tempValue = tempValue + _AxisX_step;
UILabel *tempLabel = [[UILabel alloc] init];
tempLabel.text = [NSString stringWithFormat:LabelFormat,tempValue] ;
[_axisX_labels addObject:tempLabel];
}
}
- (void) setAxisYWithMinimumValue:(CGFloat)minVal andMaxValue:(CGFloat)maxVal toTicks:(int)numberOfTicks
{
_AxisY_minValue = minVal ;
_AxisY_maxValue = maxVal ;
_AxisY_partNumber = numberOfTicks - 1;
_AxisY_step = (float)((maxVal - minVal)/_AxisY_partNumber);
NSString *LabelFormat = self.yLabelFormat ? : @"%1.f";
CGFloat tempValue = minVal ;
UILabel *label = [[UILabel alloc] init];
label.text = [NSString stringWithFormat:LabelFormat,minVal] ;
[_axisY_labels addObject:label];
for (int i = 0 ; i < _AxisY_partNumber; i++) {
tempValue = tempValue + _AxisY_step;
UILabel *tempLabel = [[UILabel alloc] init];
tempLabel.text = [NSString stringWithFormat:LabelFormat,tempValue] ;
[_axisY_labels addObject:tempLabel];
}
}
- (NSArray*) getAxisMinMax:(NSArray*)xValues
{
float min = [xValues[0] floatValue];
float max = [xValues[0] floatValue];
for (NSNumber *number in xValues)
{
if ([number floatValue] > max)
max = [number floatValue];
if ([number floatValue] < min)
min = [number floatValue];
}
NSArray *result = @[[NSNumber numberWithFloat:min], [NSNumber numberWithFloat:max]];
return result;
}
- (void)setAxisXLabel:(NSArray *)array {
if(array.count == ++_AxisX_partNumber){
[_axisX_labels removeAllObjects];
for(int i=0;i<array.count;i++){
UILabel *label = [[UILabel alloc] init];
label.text = [array objectAtIndex:i];
[_axisX_labels addObject:label];
}
}
}
- (void)setAxisYLabel:(NSArray *)array {
if(array.count == ++_AxisY_partNumber){
[_axisY_labels removeAllObjects];
for(int i=0;i<array.count;i++){
UILabel *label = [[UILabel alloc] init];
label.text = [array objectAtIndex:i];
[_axisY_labels addObject:label];
}
}
}
- (void) vectorXSetup
{
_AxisX_partNumber += 1;
_vectorX_Size = self.frame.size.width - (_AxisX_Margin) - 15 ;
_vectorX_Steps = (_vectorX_Size) / (_AxisX_partNumber) ;
_endPointVecotrX = CGPointMake(_startPoint.x + _vectorX_Size, _startPoint.y) ;
_startPointVectorX = _startPoint ;
}
- (void) vectorYSetup
{
_AxisY_partNumber += 1;
_vectorY_Size = self.frame.size.height - (_AxisY_Margin) - 15;
_vectorY_Steps = (_vectorY_Size) / (_AxisY_partNumber);
_endPointVecotrY = CGPointMake(_startPoint.x, _startPoint.y - _vectorY_Size) ;
_startPointVectorY = _startPoint ;
}
- (void) showXLabel : (UILabel *) descriptionLabel InPosition : (CGPoint) point
{
CGRect frame = CGRectMake(point.x, point.y, 30, 10);
descriptionLabel.frame = frame;
descriptionLabel.font = _descriptionTextFont;
descriptionLabel.textColor = _descriptionTextColor;
descriptionLabel.shadowColor = _descriptionTextShadowColor;
descriptionLabel.shadowOffset = _descriptionTextShadowOffset;
descriptionLabel.textAlignment = NSTextAlignmentCenter;
descriptionLabel.backgroundColor = [UIColor clearColor];
[self addSubview:descriptionLabel];
}
- (void)setChartData:(NSArray *)data
{
__block CGFloat yFinilizeValue , xFinilizeValue;
__block CGFloat yValue , xValue;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (self.displayAnimated) {
[NSThread sleepForTimeInterval:1];
}
// update UI on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
for (PNScatterChartData *chartData in data) {
for (NSUInteger i = 0; i < chartData.itemCount; i++) {
yValue = chartData.getData(i).y;
xValue = chartData.getData(i).x;
if (!(xValue >= _AxisX_minValue && xValue <= _AxisX_maxValue) || !(yValue >= _AxisY_minValue && yValue <= _AxisY_maxValue)) {
NSLog(@"input is not in correct range.");
exit(0);
}
xFinilizeValue = [self mappingIsForAxisX:true WithValue:xValue];
yFinilizeValue = [self mappingIsForAxisX:false WithValue:yValue];
CAShapeLayer *shape = [self drawingPointsForChartData:chartData AndWithX:xFinilizeValue AndWithY:yFinilizeValue];
self.pathLayer = shape ;
[self.layer addSublayer:self.pathLayer];
[self addAnimationIfNeeded];
}
}
});
});
}
- (void)addAnimationIfNeeded{
if (self.displayAnimated) {
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
pathAnimation.duration = _duration;
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
pathAnimation.fromValue = @(0.0f);
pathAnimation.toValue = @(1.0f);
pathAnimation.fillMode = kCAFillModeForwards;
self.layer.opacity = 1;
[self.pathLayer addAnimation:pathAnimation forKey:@"fade"];
}
}
- (CGFloat) mappingIsForAxisX : (BOOL) isForAxisX WithValue : (CGFloat) value{
if (isForAxisX) {
float temp = _startPointVectorX.x + (_vectorX_Steps / 2) ;
CGFloat xPos = temp + (((value - _AxisX_minValue)/_AxisX_step) * _vectorX_Steps) ;
return xPos;
}
else {
float temp = _startPointVectorY.y - (_vectorY_Steps / 2) ;
CGFloat yPos = temp - (((value - _AxisY_minValue) /_AxisY_step) * _vectorY_Steps);
return yPos;
}
return 0;
}
#pragma mark - Update Chart Data
- (void)updateChartData:(NSArray *)data
{
_chartData = data;
// will be work in future.
}
#pragma drawing methods
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
if (_showCoordinateAxis) {
CGContextSetStrokeColorWithColor(context, [_axisColor CGColor]);
CGContextSetLineWidth(context, _axisWidth);
//drawing x vector
CGContextMoveToPoint(context, _startPoint.x, _startPoint.y);
CGContextAddLineToPoint(context, _endPointVecotrX.x, _endPointVecotrX.y);
//drawing y vector
CGContextMoveToPoint(context, _startPoint.x, _startPoint.y);
CGContextAddLineToPoint(context, _endPointVecotrY.x, _endPointVecotrY.y);
//drawing x arrow vector
CGContextMoveToPoint(context, _endPointVecotrX.x, _endPointVecotrX.y);
CGContextAddLineToPoint(context, _endPointVecotrX.x - 5, _endPointVecotrX.y + 3);
CGContextMoveToPoint(context, _endPointVecotrX.x, _endPointVecotrX.y);
CGContextAddLineToPoint(context, _endPointVecotrX.x - 5, _endPointVecotrX.y - 3);
//drawing y arrow vector
CGContextMoveToPoint(context, _endPointVecotrY.x, _endPointVecotrY.y);
CGContextAddLineToPoint(context, _endPointVecotrY.x - 3, _endPointVecotrY.y + 5);
CGContextMoveToPoint(context, _endPointVecotrY.x, _endPointVecotrY.y);
CGContextAddLineToPoint(context, _endPointVecotrY.x + 3, _endPointVecotrY.y + 5);
}
if (_showLabel) {
//drawing x steps vector and putting axis x labels
float temp = _startPointVectorX.x + (_vectorX_Steps / 2) ;
for (int i = 0; i < _axisX_labels.count; i++) {
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(temp, _startPointVectorX.y - 2)];
[path addLineToPoint:CGPointMake(temp, _startPointVectorX.y + 3)];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [path CGPath];
shapeLayer.strokeColor = [_axisColor CGColor];
shapeLayer.lineWidth = _axisWidth;
shapeLayer.fillColor = [_axisColor CGColor];
[self.horizentalLinepathLayer addObject:shapeLayer];
[self.layer addSublayer:shapeLayer];
UILabel *lb = [_axisX_labels objectAtIndex:i] ;
[self showXLabel:lb InPosition:CGPointMake(temp - 15, _startPointVectorX.y + 10 )];
temp = temp + _vectorX_Steps ;
}
//drawing y steps vector and putting axis x labels
temp = _startPointVectorY.y - (_vectorY_Steps / 2) ;
for (int i = 0; i < _axisY_labels.count; i++) {
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(_startPointVectorY.x - 3, temp)];
[path addLineToPoint:CGPointMake( _startPointVectorY.x + 2, temp)];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [path CGPath];
shapeLayer.strokeColor = [_axisColor CGColor];
shapeLayer.lineWidth = _axisWidth;
shapeLayer.fillColor = [_axisColor CGColor];
[self.verticalLineLayer addObject:shapeLayer];
[self.layer addSublayer:shapeLayer];
UILabel *lb = [_axisY_labels objectAtIndex:i];
[self showXLabel:lb InPosition:CGPointMake(_startPointVectorY.x - 30, temp - 5)];
temp = temp - _vectorY_Steps ;
}
}
CGContextDrawPath(context, kCGPathStroke);
}
- (CAShapeLayer*) drawingPointsForChartData : (PNScatterChartData *) chartData AndWithX : (CGFloat) X AndWithY : (CGFloat) Y
{
if (chartData.inflexionPointStyle == PNScatterChartPointStyleCircle) {
float radius = chartData.size;
CAShapeLayer *circle = [CAShapeLayer layer];
// Make a circular shape
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(X - radius, Y - radius, 2.0*radius, 2.0*radius)
cornerRadius:radius].CGPath;
// Configure the appearence of the circle
circle.fillColor = [chartData.fillColor CGColor];
circle.strokeColor = [chartData.strokeColor CGColor];
circle.lineWidth = 1;
// Add to parent layer
return circle;
}
else if (chartData.inflexionPointStyle == PNScatterChartPointStyleSquare) {
float side = chartData.size;
CAShapeLayer *square = [CAShapeLayer layer];
// Make a circular shape
square.path = [UIBezierPath bezierPathWithRect:CGRectMake(X - (side/2) , Y - (side/2), side, side)].CGPath ;
// Configure the apperence of the circle
square.fillColor = [chartData.fillColor CGColor];
square.strokeColor = [chartData.strokeColor CGColor];
square.lineWidth = 1;
// Add to parent layer
return square;
}
else {
// you cann add your own scatter chart point here
}
return nil ;
}
- (void) drawLineFromPoint : (CGPoint) startPoint ToPoint : (CGPoint) endPoint WithLineWith : (CGFloat) lineWidth AndWithColor : (UIColor*) color{
// call the same method on a background thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (self.displayAnimated) {
[NSThread sleepForTimeInterval:2];
}
// calculating start and end point
__block CGFloat startX = [self mappingIsForAxisX:true WithValue:startPoint.x];
__block CGFloat startY = [self mappingIsForAxisX:false WithValue:startPoint.y];
__block CGFloat endX = [self mappingIsForAxisX:true WithValue:endPoint.x];
__block CGFloat endY = [self mappingIsForAxisX:false WithValue:endPoint.y];
// update UI on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
// drawing path between two points
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(startX, startY)];
[path addLineToPoint:CGPointMake(endX, endY)];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [path CGPath];
shapeLayer.strokeColor = [color CGColor];
shapeLayer.lineWidth = lineWidth;
shapeLayer.fillColor = [color CGColor];
// adding animation to path
[self addStrokeEndAnimationIfNeededToLayer:shapeLayer];
[self.layer addSublayer:shapeLayer];
});
});
}
- (void)addStrokeEndAnimationIfNeededToLayer:(CAShapeLayer *)shapeLayer{
if (self.displayAnimated) {
CABasicAnimation *animateStrokeEnd = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
animateStrokeEnd.duration = _duration;
animateStrokeEnd.fromValue = [NSNumber numberWithFloat:0.0f];
animateStrokeEnd.toValue = [NSNumber numberWithFloat:1.0f];
[shapeLayer addAnimation:animateStrokeEnd forKey:nil];
}
}
@end
+38
View File
@@ -0,0 +1,38 @@
//
// PNScatterChartData.h
// PNChartDemo
//
// Created by Alireza Arabi on 12/4/14.
// Copyright (c) 2014 kevinzhow. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSUInteger, PNScatterChartPointStyle) {
PNScatterChartPointStyleCircle = 0,
PNScatterChartPointStyleSquare = 1,
};
@class PNScatterChartDataItem;
typedef PNScatterChartDataItem *(^LCScatterChartDataGetter)(NSUInteger item);
@interface PNScatterChartData : NSObject
@property (strong) UIColor *fillColor;
@property (strong) UIColor *strokeColor;
@property NSUInteger itemCount;
@property (copy) LCScatterChartDataGetter getData;
@property (nonatomic, assign) PNScatterChartPointStyle inflexionPointStyle;
/**
* If PNLineChartPointStyle is circle, this returns the circle's diameter.
* If PNLineChartPointStyle is square, each point is a square with each side equal in length to this value.
*/
@property (nonatomic, assign) CGFloat size;
@end
+31
View File
@@ -0,0 +1,31 @@
//
// PNScatterChartData.m
// PNChartDemo
//
// Created by Alireza Arabi on 12/4/14.
// Copyright (c) 2014 kevinzhow. All rights reserved.
//
#import "PNScatterChartData.h"
@implementation PNScatterChartData
- (id)init
{
self = [super init];
if (self) {
[self setupDefaultValues];
}
return self;
}
- (void)setupDefaultValues
{
_inflexionPointStyle = PNScatterChartPointStyleCircle;
_fillColor = [UIColor grayColor];
_strokeColor = [UIColor clearColor];
_size = 3 ;
}
@end
@@ -0,0 +1,19 @@
//
// PNScatterChartDataItem.h
// PNChartDemo
//
// Created by Alireza Arabi on 12/4/14.
// Copyright (c) 2014 kevinzhow. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface PNScatterChartDataItem : NSObject
+ (PNScatterChartDataItem *)dataItemWithX:(CGFloat)x AndWithY:(CGFloat)y;
@property (readonly) CGFloat x; // should be within the x range
@property (readonly) CGFloat y; // should be within the y range
@end
@@ -0,0 +1,37 @@
//
// PNScatterChartDataItem.m
// PNChartDemo
//
// Created by Alireza Arabi on 12/4/14.
// Copyright (c) 2014 kevinzhow. All rights reserved.
//
#import "PNScatterChartDataItem.h"
@interface PNScatterChartDataItem ()
- (id)initWithX:(CGFloat)x AndWithY:(CGFloat)y;
@property (readwrite) CGFloat x; // should be within the x range
@property (readwrite) CGFloat y; // should be within the y range
@end
@implementation PNScatterChartDataItem
+ (PNScatterChartDataItem *)dataItemWithX:(CGFloat)x AndWithY:(CGFloat)y
{
return [[PNScatterChartDataItem alloc] initWithX:x AndWithY:y];
}
- (id)initWithX:(CGFloat)x AndWithY:(CGFloat)y
{
if ((self = [super init])) {
self.x = x;
self.y = y;
}
return self;
}
@end
+38
View File
@@ -0,0 +1,38 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSInteger, UILabelCountingMethod) {
UILabelCountingMethodEaseInOut,
UILabelCountingMethodEaseIn,
UILabelCountingMethodEaseOut,
UILabelCountingMethodLinear,
UILabelCountingMethodEaseInBounce,
UILabelCountingMethodEaseOutBounce
};
typedef NSString* (^UICountingLabelFormatBlock)(CGFloat value);
typedef NSAttributedString* (^UICountingLabelAttributedFormatBlock)(CGFloat value);
@interface UICountingLabel : UILabel
@property (nonatomic, strong) NSString *format;
@property (nonatomic, assign) UILabelCountingMethod method;
@property (nonatomic, assign) NSTimeInterval animationDuration;
@property (nonatomic, copy) UICountingLabelFormatBlock formatBlock;
@property (nonatomic, copy) UICountingLabelAttributedFormatBlock attributedFormatBlock;
@property (nonatomic, copy) void (^completionBlock)(void);
-(void)countFrom:(CGFloat)startValue to:(CGFloat)endValue;
-(void)countFrom:(CGFloat)startValue to:(CGFloat)endValue withDuration:(NSTimeInterval)duration;
-(void)countFromCurrentValueTo:(CGFloat)endValue;
-(void)countFromCurrentValueTo:(CGFloat)endValue withDuration:(NSTimeInterval)duration;
-(void)countFromZeroTo:(CGFloat)endValue;
-(void)countFromZeroTo:(CGFloat)endValue withDuration:(NSTimeInterval)duration;
- (CGFloat)currentValue;
@end
+292
View File
@@ -0,0 +1,292 @@
#import <QuartzCore/QuartzCore.h>
#import "UICountingLabel.h"
#if !__has_feature(objc_arc)
#error UICountingLabel is ARC only. Either turn on ARC for the project or use -fobjc-arc flag
#endif
#pragma mark - UILabelCounter
#ifndef kUILabelCounterRate
#define kUILabelCounterRate 3.0
#endif
@protocol UILabelCounter<NSObject>
-(CGFloat)update:(CGFloat)t;
@end
@interface UILabelCounterLinear : NSObject<UILabelCounter>
@end
@interface UILabelCounterEaseIn : NSObject<UILabelCounter>
@end
@interface UILabelCounterEaseOut : NSObject<UILabelCounter>
@end
@interface UILabelCounterEaseInOut : NSObject<UILabelCounter>
@end
@interface UILabelCounterEaseInBounce : NSObject<UILabelCounter>
@end
@interface UILabelCounterEaseOutBounce : NSObject<UILabelCounter>
@end
@implementation UILabelCounterLinear
-(CGFloat)update:(CGFloat)t
{
return t;
}
@end
@implementation UILabelCounterEaseIn
-(CGFloat)update:(CGFloat)t
{
return powf(t, kUILabelCounterRate);
}
@end
@implementation UILabelCounterEaseOut
-(CGFloat)update:(CGFloat)t{
return 1.0-powf((1.0-t), kUILabelCounterRate);
}
@end
@implementation UILabelCounterEaseInOut
-(CGFloat) update: (CGFloat) t
{
t *= 2;
if (t < 1)
return 0.5f * powf (t, kUILabelCounterRate);
else
return 0.5f * (2.0f - powf(2.0 - t, kUILabelCounterRate));
}
@end
@implementation UILabelCounterEaseInBounce
-(CGFloat) update: (CGFloat) t {
if (t < 4.0 / 11.0) {
return 1.0 - (powf(11.0 / 4.0, 2) * powf(t, 2)) - t;
}
if (t < 8.0 / 11.0) {
return 1.0 - (3.0 / 4.0 + powf(11.0 / 4.0, 2) * powf(t - 6.0 / 11.0, 2)) - t;
}
if (t < 10.0 / 11.0) {
return 1.0 - (15.0 /16.0 + powf(11.0 / 4.0, 2) * powf(t - 9.0 / 11.0, 2)) - t;
}
return 1.0 - (63.0 / 64.0 + powf(11.0 / 4.0, 2) * powf(t - 21.0 / 22.0, 2)) - t;
}
@end
@implementation UILabelCounterEaseOutBounce
-(CGFloat) update: (CGFloat) t {
if (t < 4.0 / 11.0) {
return powf(11.0 / 4.0, 2) * powf(t, 2);
}
if (t < 8.0 / 11.0) {
return 3.0 / 4.0 + powf(11.0 / 4.0, 2) * powf(t - 6.0 / 11.0, 2);
}
if (t < 10.0 / 11.0) {
return 15.0 /16.0 + powf(11.0 / 4.0, 2) * powf(t - 9.0 / 11.0, 2);
}
return 63.0 / 64.0 + powf(11.0 / 4.0, 2) * powf(t - 21.0 / 22.0, 2);
}
@end
#pragma mark - UICountingLabel
@interface UICountingLabel ()
@property CGFloat startingValue;
@property CGFloat destinationValue;
@property NSTimeInterval progress;
@property NSTimeInterval lastUpdate;
@property NSTimeInterval totalTime;
@property CGFloat easingRate;
@property (nonatomic, strong) CADisplayLink *timer;
@property (nonatomic, strong) id<UILabelCounter> counter;
@end
@implementation UICountingLabel
-(void)countFrom:(CGFloat)value to:(CGFloat)endValue {
if (self.animationDuration == 0.0f) {
self.animationDuration = 2.0f;
}
[self countFrom:value to:endValue withDuration:self.animationDuration];
}
-(void)countFrom:(CGFloat)startValue to:(CGFloat)endValue withDuration:(NSTimeInterval)duration {
self.startingValue = startValue;
self.destinationValue = endValue;
// remove any (possible) old timers
[self.timer invalidate];
self.timer = nil;
if(self.format == nil) {
self.format = @"%f";
}
if (duration == 0.0) {
// No animation
[self setTextValue:endValue];
[self runCompletionBlock];
return;
}
self.easingRate = 3.0f;
self.progress = 0;
self.totalTime = duration;
self.lastUpdate = [NSDate timeIntervalSinceReferenceDate];
switch(self.method)
{
case UILabelCountingMethodLinear:
self.counter = [[UILabelCounterLinear alloc] init];
break;
case UILabelCountingMethodEaseIn:
self.counter = [[UILabelCounterEaseIn alloc] init];
break;
case UILabelCountingMethodEaseOut:
self.counter = [[UILabelCounterEaseOut alloc] init];
break;
case UILabelCountingMethodEaseInOut:
self.counter = [[UILabelCounterEaseInOut alloc] init];
break;
case UILabelCountingMethodEaseOutBounce:
self.counter = [[UILabelCounterEaseOutBounce alloc] init];
break;
case UILabelCountingMethodEaseInBounce:
self.counter = [[UILabelCounterEaseInBounce alloc] init];
break;
}
CADisplayLink *timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateValue:)];
timer.frameInterval = 2;
[timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:UITrackingRunLoopMode];
self.timer = timer;
}
- (void)countFromCurrentValueTo:(CGFloat)endValue {
[self countFrom:[self currentValue] to:endValue];
}
- (void)countFromCurrentValueTo:(CGFloat)endValue withDuration:(NSTimeInterval)duration {
[self countFrom:[self currentValue] to:endValue withDuration:duration];
}
- (void)countFromZeroTo:(CGFloat)endValue {
[self countFrom:0.0f to:endValue];
}
- (void)countFromZeroTo:(CGFloat)endValue withDuration:(NSTimeInterval)duration {
[self countFrom:0.0f to:endValue withDuration:duration];
}
- (void)updateValue:(NSTimer *)timer {
// update progress
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
self.progress += now - self.lastUpdate;
self.lastUpdate = now;
if (self.progress >= self.totalTime) {
[self.timer invalidate];
self.timer = nil;
self.progress = self.totalTime;
}
[self setTextValue:[self currentValue]];
if (self.progress == self.totalTime) {
[self runCompletionBlock];
}
}
- (void)setTextValue:(CGFloat)value
{
if (self.attributedFormatBlock != nil) {
self.attributedText = self.attributedFormatBlock(value);
}
else if(self.formatBlock != nil)
{
self.text = self.formatBlock(value);
}
else
{
// check if counting with ints - cast to int
if([self.format rangeOfString:@"%(.*)d" options:NSRegularExpressionSearch].location != NSNotFound || [self.format rangeOfString:@"%(.*)i"].location != NSNotFound )
{
self.text = [NSString stringWithFormat:self.format,(int)value];
}
else
{
self.text = [NSString stringWithFormat:self.format,value];
}
}
}
- (void)setFormat:(NSString *)format {
_format = format;
// update label with new format
[self setTextValue:self.currentValue];
}
- (void)runCompletionBlock {
if (self.completionBlock) {
self.completionBlock();
self.completionBlock = nil;
}
}
- (CGFloat)currentValue {
if (self.progress >= self.totalTime) {
return self.destinationValue;
}
CGFloat percent = self.progress / self.totalTime;
CGFloat updateVal = [self.counter update:percent];
return self.startingValue + (updateVal * (self.destinationValue - self.startingValue));
}
@end