Kevin

Merge pull request #164 from ZhangHang/master

PieChart divides equally when all value of data items are 0
@@ -10,21 +10,24 @@ @@ -10,21 +10,24 @@
10 10
11 @interface PNPieChart() 11 @interface PNPieChart()
12 12
13 -@property (nonatomic, readwrite) NSArray *items; 13 +@property (nonatomic) NSArray *items;
14 -@property (nonatomic) CGFloat total; 14 +@property (nonatomic) NSArray *endPercentages;
15 -@property (nonatomic) CGFloat currentTotal;  
16 15
17 @property (nonatomic) CGFloat outerCircleRadius; 16 @property (nonatomic) CGFloat outerCircleRadius;
18 @property (nonatomic) CGFloat innerCircleRadius; 17 @property (nonatomic) CGFloat innerCircleRadius;
19 18
20 -@property (nonatomic) UIView *contentView; 19 +@property (nonatomic) UIView *contentView;
21 -@property (nonatomic) CAShapeLayer *pieLayer; 20 +@property (nonatomic) CAShapeLayer *pieLayer;
22 @property (nonatomic) NSMutableArray *descriptionLabels; 21 @property (nonatomic) NSMutableArray *descriptionLabels;
23 22
  23 +
24 - (void)loadDefault; 24 - (void)loadDefault;
25 25
26 - (UILabel *)descriptionLabelForItemAtIndex:(NSUInteger)index; 26 - (UILabel *)descriptionLabelForItemAtIndex:(NSUInteger)index;
27 - (PNPieChartDataItem *)dataItemForIndex:(NSUInteger)index; 27 - (PNPieChartDataItem *)dataItemForIndex:(NSUInteger)index;
  28 +- (CGFloat)startPercentageForItemAtIndex:(NSUInteger)index;
  29 +- (CGFloat)endPercentageForItemAtIndex:(NSUInteger)index;
  30 +- (CGFloat)ratioForItemAtIndex:(NSUInteger)index;
28 31
29 - (CAShapeLayer *)newCircleLayerWithRadius:(CGFloat)radius 32 - (CAShapeLayer *)newCircleLayerWithRadius:(CGFloat)radius
30 borderWidth:(CGFloat)borderWidth 33 borderWidth:(CGFloat)borderWidth
@@ -40,122 +43,129 @@ @@ -40,122 +43,129 @@
40 @implementation PNPieChart 43 @implementation PNPieChart
41 44
42 -(id)initWithFrame:(CGRect)frame items:(NSArray *)items{ 45 -(id)initWithFrame:(CGRect)frame items:(NSArray *)items{
43 - self = [self initWithFrame:frame]; 46 + self = [self initWithFrame:frame];
44 - if(self){ 47 + if(self){
45 - _items = [NSArray arrayWithArray:items]; 48 + _items = [NSArray arrayWithArray:items];
46 - _outerCircleRadius = CGRectGetWidth(self.bounds)/2; 49 + _outerCircleRadius = CGRectGetWidth(self.bounds) / 2;
47 - _innerCircleRadius = CGRectGetWidth(self.bounds)/6; 50 + _innerCircleRadius = CGRectGetWidth(self.bounds) / 6;
48 - 51 +
49 - _descriptionTextColor = [UIColor whiteColor]; 52 + _descriptionTextColor = [UIColor whiteColor];
50 - _descriptionTextFont = [UIFont fontWithName:@"Avenir-Medium" size:18.0]; 53 + _descriptionTextFont = [UIFont fontWithName:@"Avenir-Medium" size:18.0];
51 - _descriptionTextShadowColor = [[UIColor blackColor] colorWithAlphaComponent:0.4]; 54 + _descriptionTextShadowColor = [[UIColor blackColor] colorWithAlphaComponent:0.4];
52 _descriptionTextShadowOffset = CGSizeMake(0, 1); 55 _descriptionTextShadowOffset = CGSizeMake(0, 1);
53 - _duration = 1.0; 56 + _duration = 1.0;
54 57
55 - [self loadDefault]; 58 + [self loadDefault];
56 - } 59 + }
57 - 60 +
58 - return self; 61 + return self;
59 } 62 }
60 63
61 -  
62 - (void)loadDefault{ 64 - (void)loadDefault{
63 - _currentTotal = 0; 65 + __block CGFloat currentTotal = 0;
64 - _total = 0; 66 + CGFloat total = [[self.items valueForKeyPath:@"@sum.value"] floatValue];
65 - 67 + NSMutableArray *endPercentages = [NSMutableArray new];
66 - [_contentView removeFromSuperview]; 68 + [_items enumerateObjectsUsingBlock:^(PNPieChartDataItem *item, NSUInteger idx, BOOL *stop) {
67 - _contentView = [[UIView alloc] initWithFrame:self.bounds]; 69 + if (total == 0){
68 - [self addSubview:_contentView]; 70 + [endPercentages addObject:@(1.0 / _items.count * (idx + 1))];
69 - [_descriptionLabels removeAllObjects]; 71 + }else{
70 - _descriptionLabels = [NSMutableArray new]; 72 + currentTotal += item.value;
71 - 73 + [endPercentages addObject:@(currentTotal / total)];
72 - _pieLayer = [CAShapeLayer layer]; 74 + }
73 - [_contentView.layer addSublayer:_pieLayer]; 75 + }];
  76 + self.endPercentages = [endPercentages copy];
  77 +
  78 + [_contentView removeFromSuperview];
  79 + _contentView = [[UIView alloc] initWithFrame:self.bounds];
  80 + [self addSubview:_contentView];
  81 + _descriptionLabels = [NSMutableArray new];
  82 +
  83 + _pieLayer = [CAShapeLayer layer];
  84 + [_contentView.layer addSublayer:_pieLayer];
74 } 85 }
75 86
76 #pragma mark - 87 #pragma mark -
77 88
78 - (void)strokeChart{ 89 - (void)strokeChart{
79 - [self loadDefault]; 90 + [self loadDefault];
80 - 91 +
81 - [self.items enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 92 + PNPieChartDataItem *currentItem;
82 - _total +=((PNPieChartDataItem *)obj).value; 93 + for (int i = 0; i < _items.count; i++) {
83 - }]; 94 + currentItem = [self dataItemForIndex:i];
84 - 95 +
85 - PNPieChartDataItem *currentItem; 96 +
86 - CGFloat currentValue = 0; 97 + CGFloat startPercnetage = [self startPercentageForItemAtIndex:i];
87 - for (int i = 0; i < _items.count; i++) { 98 + CGFloat endPercentage = [self endPercentageForItemAtIndex:i];
88 - currentItem = [self dataItemForIndex:i]; 99 +
89 - 100 + CGFloat radius = _innerCircleRadius + (_outerCircleRadius - _innerCircleRadius) / 2;
90 - 101 + CGFloat borderWidth = _outerCircleRadius - _innerCircleRadius;
91 - CGFloat startPercnetage = currentValue/_total; 102 + CAShapeLayer *currentPieLayer = [self newCircleLayerWithRadius:radius
92 - CGFloat endPercentage = (currentValue + currentItem.value)/_total; 103 + borderWidth:borderWidth
93 -  
94 - CAShapeLayer *currentPieLayer = [self newCircleLayerWithRadius:_innerCircleRadius + (_outerCircleRadius - _innerCircleRadius)/2  
95 - borderWidth:_outerCircleRadius - _innerCircleRadius  
96 fillColor:[UIColor clearColor] 104 fillColor:[UIColor clearColor]
97 borderColor:currentItem.color 105 borderColor:currentItem.color
98 startPercentage:startPercnetage 106 startPercentage:startPercnetage
99 endPercentage:endPercentage]; 107 endPercentage:endPercentage];
100 - [_pieLayer addSublayer:currentPieLayer]; 108 + [_pieLayer addSublayer:currentPieLayer];
101 - 109 + }
102 - currentValue+=currentItem.value; 110 +
103 - 111 + [self maskChart];
104 - } 112 +
105 -  
106 - [self maskChart];  
107 -  
108 - currentValue = 0;  
109 for (int i = 0; i < _items.count; i++) { 113 for (int i = 0; i < _items.count; i++) {
110 - currentItem = [self dataItemForIndex:i]; 114 + UILabel *descriptionLabel = [self descriptionLabelForItemAtIndex:i];
111 - UILabel *descriptionLabel = [self descriptionLabelForItemAtIndex:i]; 115 + [_contentView addSubview:descriptionLabel];
112 - [_contentView addSubview:descriptionLabel];  
113 - currentValue+=currentItem.value;  
114 [_descriptionLabels addObject:descriptionLabel]; 116 [_descriptionLabels addObject:descriptionLabel];
115 - } 117 + }
116 } 118 }
117 119
118 - (UILabel *)descriptionLabelForItemAtIndex:(NSUInteger)index{ 120 - (UILabel *)descriptionLabelForItemAtIndex:(NSUInteger)index{
119 - PNPieChartDataItem *currentDataItem = [self dataItemForIndex:index]; 121 + PNPieChartDataItem *currentDataItem = [self dataItemForIndex:index];
120 CGFloat distance = _innerCircleRadius + (_outerCircleRadius - _innerCircleRadius) / 2; 122 CGFloat distance = _innerCircleRadius + (_outerCircleRadius - _innerCircleRadius) / 2;
121 - CGFloat centerPercentage =(_currentTotal + currentDataItem.value /2 ) / _total; 123 + CGFloat centerPercentage = ([self startPercentageForItemAtIndex:index] + [self endPercentageForItemAtIndex:index])/ 2;
122 CGFloat rad = centerPercentage * 2 * M_PI; 124 CGFloat rad = centerPercentage * 2 * M_PI;
123 125
124 - _currentTotal += currentDataItem.value;  
125 -  
126 UILabel *descriptionLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 80)]; 126 UILabel *descriptionLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 80)];
127 - NSString *titleText = currentDataItem.textDescription; 127 + NSString *titleText = [NSString stringWithFormat:@"%.0f%%",[self ratioForItemAtIndex:index] * 100];
128 - if(!titleText){ 128 + if(currentDataItem.textDescription){
129 - titleText = [NSString stringWithFormat:@"%.0f%%",currentDataItem.value/ _total * 100]; 129 + titleText = [titleText stringByAppendingFormat:@"\n%@",currentDataItem.textDescription];
130 - descriptionLabel.text = titleText ;  
131 - }  
132 - else {  
133 - NSString* str = [NSString stringWithFormat:@"%.0f%%\n",currentDataItem.value/ _total * 100];  
134 - str = [str stringByAppendingString:titleText];  
135 - descriptionLabel.text = str ;  
136 } 130 }
  131 + descriptionLabel.text = titleText;
137 132
138 CGPoint center = CGPointMake(_outerCircleRadius + distance * sin(rad), 133 CGPoint center = CGPointMake(_outerCircleRadius + distance * sin(rad),
139 _outerCircleRadius - distance * cos(rad)); 134 _outerCircleRadius - distance * cos(rad));
140 135
141 descriptionLabel.font = _descriptionTextFont; 136 descriptionLabel.font = _descriptionTextFont;
142 CGSize labelSize = [descriptionLabel.text sizeWithAttributes:@{NSFontAttributeName:descriptionLabel.font}]; 137 CGSize labelSize = [descriptionLabel.text sizeWithAttributes:@{NSFontAttributeName:descriptionLabel.font}];
143 - descriptionLabel.frame = CGRectMake( 138 + descriptionLabel.frame = CGRectMake(descriptionLabel.frame.origin.x, descriptionLabel.frame.origin.y,
144 - descriptionLabel.frame.origin.x, descriptionLabel.frame.origin.y, 139 + descriptionLabel.frame.size.width, labelSize.height);
145 - descriptionLabel.frame.size.width, labelSize.height); 140 + descriptionLabel.numberOfLines = 0;
146 - descriptionLabel.numberOfLines = 0; 141 + descriptionLabel.textColor = _descriptionTextColor;
147 - descriptionLabel.textColor = _descriptionTextColor; 142 + descriptionLabel.shadowColor = _descriptionTextShadowColor;
148 - descriptionLabel.shadowColor = _descriptionTextShadowColor; 143 + descriptionLabel.shadowOffset = _descriptionTextShadowOffset;
149 - descriptionLabel.shadowOffset = _descriptionTextShadowOffset; 144 + descriptionLabel.textAlignment = NSTextAlignmentCenter;
150 - descriptionLabel.textAlignment = NSTextAlignmentCenter; 145 + descriptionLabel.center = center;
151 - descriptionLabel.center = center; 146 + descriptionLabel.alpha = 0;
152 - descriptionLabel.alpha = 0;  
153 descriptionLabel.backgroundColor = [UIColor clearColor]; 147 descriptionLabel.backgroundColor = [UIColor clearColor];
154 - return descriptionLabel; 148 + return descriptionLabel;
155 } 149 }
156 150
157 - (PNPieChartDataItem *)dataItemForIndex:(NSUInteger)index{ 151 - (PNPieChartDataItem *)dataItemForIndex:(NSUInteger)index{
158 - return self.items[index]; 152 + return self.items[index];
  153 +}
  154 +
  155 +- (CGFloat)startPercentageForItemAtIndex:(NSUInteger)index{
  156 + if(index == 0){
  157 + return 0;
  158 + }
  159 +
  160 + return [_endPercentages[index - 1] floatValue];
  161 +}
  162 +
  163 +- (CGFloat)endPercentageForItemAtIndex:(NSUInteger)index{
  164 + return [_endPercentages[index] floatValue];
  165 +}
  166 +
  167 +- (CGFloat)ratioForItemAtIndex:(NSUInteger)index{
  168 + return [self endPercentageForItemAtIndex:index] - [self startPercentageForItemAtIndex:index];
159 } 169 }
160 170
161 #pragma mark private methods 171 #pragma mark private methods
@@ -183,38 +193,42 @@ @@ -183,38 +193,42 @@
183 circle.lineWidth = borderWidth; 193 circle.lineWidth = borderWidth;
184 circle.path = path.CGPath; 194 circle.path = path.CGPath;
185 195
186 - 196 + return circle;
187 - return circle;  
188 } 197 }
189 198
190 - (void)maskChart{ 199 - (void)maskChart{
191 - CAShapeLayer *maskLayer = [self newCircleLayerWithRadius:_innerCircleRadius + (_outerCircleRadius - _innerCircleRadius)/2 200 + CGFloat radius = _innerCircleRadius + (_outerCircleRadius - _innerCircleRadius) / 2;
192 - borderWidth:_outerCircleRadius - _innerCircleRadius 201 + CGFloat borderWidth = _outerCircleRadius - _innerCircleRadius;
  202 + CAShapeLayer *maskLayer = [self newCircleLayerWithRadius:radius
  203 + borderWidth:borderWidth
193 fillColor:[UIColor clearColor] 204 fillColor:[UIColor clearColor]
194 borderColor:[UIColor blackColor] 205 borderColor:[UIColor blackColor]
195 startPercentage:0 206 startPercentage:0
196 endPercentage:1]; 207 endPercentage:1];
197 - 208 +
198 - _pieLayer.mask = maskLayer; 209 + _pieLayer.mask = maskLayer;
199 - CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; 210 + CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
200 - animation.duration = _duration; 211 + animation.duration = _duration;
201 - animation.fromValue = @0; 212 + animation.fromValue = @0;
202 - animation.toValue = @1; 213 + animation.toValue = @1;
203 animation.delegate = self; 214 animation.delegate = self;
204 - animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 215 + animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
205 - animation.removedOnCompletion = YES; 216 + animation.removedOnCompletion = YES;
206 - [maskLayer addAnimation:animation forKey:@"circleAnimation"]; 217 + [maskLayer addAnimation:animation forKey:@"circleAnimation"];
207 } 218 }
208 219
209 -- (void)createArcAnimationForLayer:(CAShapeLayer *)layer ForKey:(NSString *)key fromValue:(NSNumber *)from toValue:(NSNumber *)to Delegate:(id)delegate 220 +- (void)createArcAnimationForLayer:(CAShapeLayer *)layer
210 -{ 221 + forKey:(NSString *)key
211 - CABasicAnimation *arcAnimation = [CABasicAnimation animationWithKeyPath:key]; 222 + fromValue:(NSNumber *)from
212 - arcAnimation.fromValue = @0; 223 + toValue:(NSNumber *)to
213 - [arcAnimation setToValue:to]; 224 + delegate:(id)delegate{
214 - [arcAnimation setDelegate:delegate]; 225 + CABasicAnimation *arcAnimation = [CABasicAnimation animationWithKeyPath:key];
215 - [arcAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]]; 226 + arcAnimation.fromValue = @0;
216 - [layer addAnimation:arcAnimation forKey:key]; 227 + arcAnimation.toValue = to;
217 - [layer setValue:to forKey:key]; 228 + arcAnimation.delegate = delegate;
  229 + arcAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
  230 + [layer addAnimation:arcAnimation forKey:key];
  231 + [layer setValue:to forKey:key];
218 } 232 }
219 233
220 - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{ 234 - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
@@ -27,4 +27,11 @@ @@ -27,4 +27,11 @@
27 return item; 27 return item;
28 } 28 }
29 29
  30 +- (void)setValue:(CGFloat)value{
  31 + NSAssert(value >= 0, @"value should >= 0");
  32 + if (value != _value){
  33 + _value = value;
  34 + }
  35 +}
  36 +
30 @end 37 @end