Kevin

Merge pull request #164 from ZhangHang/master

PieChart divides equally when all value of data items are 0
@@ -10,9 +10,8 @@ @@ -10,9 +10,8 @@
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;
@@ -21,10 +20,14 @@ @@ -21,10 +20,14 @@
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
@@ -43,8 +46,8 @@ @@ -43,8 +46,8 @@
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];
@@ -58,15 +61,23 @@ @@ -58,15 +61,23 @@
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];
  67 + NSMutableArray *endPercentages = [NSMutableArray new];
  68 + [_items enumerateObjectsUsingBlock:^(PNPieChartDataItem *item, NSUInteger idx, BOOL *stop) {
  69 + if (total == 0){
  70 + [endPercentages addObject:@(1.0 / _items.count * (idx + 1))];
  71 + }else{
  72 + currentTotal += item.value;
  73 + [endPercentages addObject:@(currentTotal / total)];
  74 + }
  75 + }];
  76 + self.endPercentages = [endPercentages copy];
65 77
66 [_contentView removeFromSuperview]; 78 [_contentView removeFromSuperview];
67 _contentView = [[UIView alloc] initWithFrame:self.bounds]; 79 _contentView = [[UIView alloc] initWithFrame:self.bounds];
68 [self addSubview:_contentView]; 80 [self addSubview:_contentView];
69 - [_descriptionLabels removeAllObjects];  
70 _descriptionLabels = [NSMutableArray new]; 81 _descriptionLabels = [NSMutableArray new];
71 82
72 _pieLayer = [CAShapeLayer layer]; 83 _pieLayer = [CAShapeLayer layer];
@@ -78,39 +89,30 @@ @@ -78,39 +89,30 @@
78 - (void)strokeChart{ 89 - (void)strokeChart{
79 [self loadDefault]; 90 [self loadDefault];
80 91
81 - [self.items enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {  
82 - _total +=((PNPieChartDataItem *)obj).value;  
83 - }];  
84 -  
85 PNPieChartDataItem *currentItem; 92 PNPieChartDataItem *currentItem;
86 - CGFloat currentValue = 0;  
87 for (int i = 0; i < _items.count; i++) { 93 for (int i = 0; i < _items.count; i++) {
88 currentItem = [self dataItemForIndex:i]; 94 currentItem = [self dataItemForIndex:i];
89 95
90 96
91 - CGFloat startPercnetage = currentValue/_total; 97 + CGFloat startPercnetage = [self startPercentageForItemAtIndex:i];
92 - CGFloat endPercentage = (currentValue + currentItem.value)/_total; 98 + CGFloat endPercentage = [self endPercentageForItemAtIndex:i];
93 99
94 - CAShapeLayer *currentPieLayer = [self newCircleLayerWithRadius:_innerCircleRadius + (_outerCircleRadius - _innerCircleRadius)/2 100 + CGFloat radius = _innerCircleRadius + (_outerCircleRadius - _innerCircleRadius) / 2;
95 - borderWidth:_outerCircleRadius - _innerCircleRadius 101 + CGFloat borderWidth = _outerCircleRadius - _innerCircleRadius;
  102 + CAShapeLayer *currentPieLayer = [self newCircleLayerWithRadius:radius
  103 + borderWidth:borderWidth
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 -  
102 - currentValue+=currentItem.value;  
103 -  
104 } 109 }
105 110
106 [self maskChart]; 111 [self maskChart];
107 112
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];  
111 UILabel *descriptionLabel = [self descriptionLabelForItemAtIndex:i]; 114 UILabel *descriptionLabel = [self descriptionLabelForItemAtIndex:i];
112 [_contentView addSubview:descriptionLabel]; 115 [_contentView addSubview:descriptionLabel];
113 - currentValue+=currentItem.value;  
114 [_descriptionLabels addObject:descriptionLabel]; 116 [_descriptionLabels addObject:descriptionLabel];
115 } 117 }
116 } 118 }
@@ -118,30 +120,22 @@ @@ -118,30 +120,22 @@
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,  
145 descriptionLabel.frame.size.width, labelSize.height); 139 descriptionLabel.frame.size.width, labelSize.height);
146 descriptionLabel.numberOfLines = 0; 140 descriptionLabel.numberOfLines = 0;
147 descriptionLabel.textColor = _descriptionTextColor; 141 descriptionLabel.textColor = _descriptionTextColor;
@@ -158,6 +152,22 @@ @@ -158,6 +152,22 @@
158 return self.items[index]; 152 return self.items[index];
159 } 153 }
160 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];
  169 +}
  170 +
161 #pragma mark private methods 171 #pragma mark private methods
162 172
163 - (CAShapeLayer *)newCircleLayerWithRadius:(CGFloat)radius 173 - (CAShapeLayer *)newCircleLayerWithRadius:(CGFloat)radius
@@ -183,13 +193,14 @@ @@ -183,13 +193,14 @@
183 circle.lineWidth = borderWidth; 193 circle.lineWidth = borderWidth;
184 circle.path = path.CGPath; 194 circle.path = path.CGPath;
185 195
186 -  
187 return circle; 196 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
@@ -206,13 +217,16 @@ @@ -206,13 +217,16 @@
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
  222 + fromValue:(NSNumber *)from
  223 + toValue:(NSNumber *)to
  224 + delegate:(id)delegate{
211 CABasicAnimation *arcAnimation = [CABasicAnimation animationWithKeyPath:key]; 225 CABasicAnimation *arcAnimation = [CABasicAnimation animationWithKeyPath:key];
212 arcAnimation.fromValue = @0; 226 arcAnimation.fromValue = @0;
213 - [arcAnimation setToValue:to]; 227 + arcAnimation.toValue = to;
214 - [arcAnimation setDelegate:delegate]; 228 + arcAnimation.delegate = delegate;
215 - [arcAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]]; 229 + arcAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
216 [layer addAnimation:arcAnimation forKey:key]; 230 [layer addAnimation:arcAnimation forKey:key];
217 [layer setValue:to forKey:key]; 231 [layer setValue:to forKey:key];
218 } 232 }
@@ -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