andi

merge

@@ -157,7 +157,7 @@ displayCountingLabel:(BOOL)displayCountingLabel @@ -157,7 +157,7 @@ displayCountingLabel:(BOOL)displayCountingLabel
157 [_circle addAnimation:pathAnimation forKey:@"strokeEndAnimation"]; 157 [_circle addAnimation:pathAnimation forKey:@"strokeEndAnimation"];
158 _circle.strokeEnd = [_current floatValue] / [_total floatValue]; 158 _circle.strokeEnd = [_current floatValue] / [_total floatValue];
159 159
160 - [_countingLabel countFrom:0 to:[_current floatValue] withDuration:self.duration]; 160 + [_countingLabel countFrom:0 to:[_current floatValue]/([_total floatValue]/100.0) withDuration:self.duration];
161 161
162 162
163 // Check if user wants to add a gradient from the start color to the bar color 163 // Check if user wants to add a gradient from the start color to the bar color
@@ -104,6 +104,20 @@ @@ -104,6 +104,20 @@
104 } 104 }
105 } 105 }
106 106
  107 +- (CGFloat)computeEqualWidthForXLabels:(NSArray *)xLabels
  108 +{
  109 + CGFloat xLabelWidth;
  110 +
  111 + if (_showLabel) {
  112 + xLabelWidth = _chartCavanWidth / [xLabels count];
  113 + } else {
  114 + xLabelWidth = (self.frame.size.width) / [xLabels count];
  115 + }
  116 +
  117 + return xLabelWidth;
  118 +}
  119 +
  120 +
107 - (void)setXLabels:(NSArray *)xLabels 121 - (void)setXLabels:(NSArray *)xLabels
108 { 122 {
109 CGFloat xLabelWidth; 123 CGFloat xLabelWidth;
@@ -12,9 +12,8 @@ @@ -12,9 +12,8 @@
12 12
13 @interface PNPieChart() 13 @interface PNPieChart()
14 14
15 -@property (nonatomic, readwrite) NSArray *items; 15 +@property (nonatomic) NSArray *items;
16 -@property (nonatomic) CGFloat total; 16 +@property (nonatomic) NSArray *endPercentages;
17 -@property (nonatomic) CGFloat currentTotal;  
18 17
19 @property (nonatomic) CGFloat outerCircleRadius; 18 @property (nonatomic) CGFloat outerCircleRadius;
20 @property (nonatomic) CGFloat innerCircleRadius; 19 @property (nonatomic) CGFloat innerCircleRadius;
@@ -23,10 +22,14 @@ @@ -23,10 +22,14 @@
23 @property (nonatomic) CAShapeLayer *pieLayer; 22 @property (nonatomic) CAShapeLayer *pieLayer;
24 @property (nonatomic) NSMutableArray *descriptionLabels; 23 @property (nonatomic) NSMutableArray *descriptionLabels;
25 24
  25 +
26 - (void)loadDefault; 26 - (void)loadDefault;
27 27
28 - (UILabel *)descriptionLabelForItemAtIndex:(NSUInteger)index; 28 - (UILabel *)descriptionLabelForItemAtIndex:(NSUInteger)index;
29 - (PNPieChartDataItem *)dataItemForIndex:(NSUInteger)index; 29 - (PNPieChartDataItem *)dataItemForIndex:(NSUInteger)index;
  30 +- (CGFloat)startPercentageForItemAtIndex:(NSUInteger)index;
  31 +- (CGFloat)endPercentageForItemAtIndex:(NSUInteger)index;
  32 +- (CGFloat)ratioForItemAtIndex:(NSUInteger)index;
30 33
31 - (CAShapeLayer *)newCircleLayerWithRadius:(CGFloat)radius 34 - (CAShapeLayer *)newCircleLayerWithRadius:(CGFloat)radius
32 borderWidth:(CGFloat)borderWidth 35 borderWidth:(CGFloat)borderWidth
@@ -45,8 +48,8 @@ @@ -45,8 +48,8 @@
45 self = [self initWithFrame:frame]; 48 self = [self initWithFrame:frame];
46 if(self){ 49 if(self){
47 _items = [NSArray arrayWithArray:items]; 50 _items = [NSArray arrayWithArray:items];
48 - _outerCircleRadius = CGRectGetWidth(self.bounds)/2; 51 + _outerCircleRadius = CGRectGetWidth(self.bounds) / 2;
49 - _innerCircleRadius = CGRectGetWidth(self.bounds)/6; 52 + _innerCircleRadius = CGRectGetWidth(self.bounds) / 6;
50 53
51 _descriptionTextColor = [UIColor whiteColor]; 54 _descriptionTextColor = [UIColor whiteColor];
52 _descriptionTextFont = [UIFont fontWithName:@"Avenir-Medium" size:18.0]; 55 _descriptionTextFont = [UIFont fontWithName:@"Avenir-Medium" size:18.0];
@@ -60,15 +63,23 @@ @@ -60,15 +63,23 @@
60 return self; 63 return self;
61 } 64 }
62 65
63 -  
64 - (void)loadDefault{ 66 - (void)loadDefault{
65 - _currentTotal = 0; 67 + __block CGFloat currentTotal = 0;
66 - _total = 0; 68 + CGFloat total = [[self.items valueForKeyPath:@"@sum.value"] floatValue];
  69 + NSMutableArray *endPercentages = [NSMutableArray new];
  70 + [_items enumerateObjectsUsingBlock:^(PNPieChartDataItem *item, NSUInteger idx, BOOL *stop) {
  71 + if (total == 0){
  72 + [endPercentages addObject:@(1.0 / _items.count * (idx + 1))];
  73 + }else{
  74 + currentTotal += item.value;
  75 + [endPercentages addObject:@(currentTotal / total)];
  76 + }
  77 + }];
  78 + self.endPercentages = [endPercentages copy];
67 79
68 [_contentView removeFromSuperview]; 80 [_contentView removeFromSuperview];
69 _contentView = [[UIView alloc] initWithFrame:self.bounds]; 81 _contentView = [[UIView alloc] initWithFrame:self.bounds];
70 [self addSubview:_contentView]; 82 [self addSubview:_contentView];
71 - [_descriptionLabels removeAllObjects];  
72 _descriptionLabels = [NSMutableArray new]; 83 _descriptionLabels = [NSMutableArray new];
73 84
74 _pieLayer = [CAShapeLayer layer]; 85 _pieLayer = [CAShapeLayer layer];
@@ -80,39 +91,30 @@ @@ -80,39 +91,30 @@
80 - (void)strokeChart{ 91 - (void)strokeChart{
81 [self loadDefault]; 92 [self loadDefault];
82 93
83 - [self.items enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {  
84 - _total +=((PNPieChartDataItem *)obj).value;  
85 - }];  
86 -  
87 PNPieChartDataItem *currentItem; 94 PNPieChartDataItem *currentItem;
88 - CGFloat currentValue = 0;  
89 for (int i = 0; i < _items.count; i++) { 95 for (int i = 0; i < _items.count; i++) {
90 currentItem = [self dataItemForIndex:i]; 96 currentItem = [self dataItemForIndex:i];
91 97
92 98
93 - CGFloat startPercnetage = currentValue/_total; 99 + CGFloat startPercnetage = [self startPercentageForItemAtIndex:i];
94 - CGFloat endPercentage = (currentValue + currentItem.value)/_total; 100 + CGFloat endPercentage = [self endPercentageForItemAtIndex:i];
95 101
96 - CAShapeLayer *currentPieLayer = [self newCircleLayerWithRadius:_innerCircleRadius + (_outerCircleRadius - _innerCircleRadius)/2 102 + CGFloat radius = _innerCircleRadius + (_outerCircleRadius - _innerCircleRadius) / 2;
97 - borderWidth:_outerCircleRadius - _innerCircleRadius 103 + CGFloat borderWidth = _outerCircleRadius - _innerCircleRadius;
  104 + CAShapeLayer *currentPieLayer = [self newCircleLayerWithRadius:radius
  105 + borderWidth:borderWidth
98 fillColor:[UIColor clearColor] 106 fillColor:[UIColor clearColor]
99 borderColor:currentItem.color 107 borderColor:currentItem.color
100 startPercentage:startPercnetage 108 startPercentage:startPercnetage
101 endPercentage:endPercentage]; 109 endPercentage:endPercentage];
102 [_pieLayer addSublayer:currentPieLayer]; 110 [_pieLayer addSublayer:currentPieLayer];
103 -  
104 - currentValue+=currentItem.value;  
105 -  
106 } 111 }
107 112
108 [self maskChart]; 113 [self maskChart];
109 114
110 - currentValue = 0;  
111 for (int i = 0; i < _items.count; i++) { 115 for (int i = 0; i < _items.count; i++) {
112 - currentItem = [self dataItemForIndex:i];  
113 UILabel *descriptionLabel = [self descriptionLabelForItemAtIndex:i]; 116 UILabel *descriptionLabel = [self descriptionLabelForItemAtIndex:i];
114 [_contentView addSubview:descriptionLabel]; 117 [_contentView addSubview:descriptionLabel];
115 - currentValue+=currentItem.value;  
116 [_descriptionLabels addObject:descriptionLabel]; 118 [_descriptionLabels addObject:descriptionLabel];
117 } 119 }
118 } 120 }
@@ -120,11 +122,9 @@ @@ -120,11 +122,9 @@
120 - (UILabel *)descriptionLabelForItemAtIndex:(NSUInteger)index{ 122 - (UILabel *)descriptionLabelForItemAtIndex:(NSUInteger)index{
121 PNPieChartDataItem *currentDataItem = [self dataItemForIndex:index]; 123 PNPieChartDataItem *currentDataItem = [self dataItemForIndex:index];
122 CGFloat distance = _innerCircleRadius + (_outerCircleRadius - _innerCircleRadius) / 2; 124 CGFloat distance = _innerCircleRadius + (_outerCircleRadius - _innerCircleRadius) / 2;
123 - CGFloat centerPercentage =(_currentTotal + currentDataItem.value /2 ) / _total; 125 + CGFloat centerPercentage = ([self startPercentageForItemAtIndex:index] + [self endPercentageForItemAtIndex:index])/ 2;
124 CGFloat rad = centerPercentage * 2 * M_PI; 126 CGFloat rad = centerPercentage * 2 * M_PI;
125 127
126 - _currentTotal += currentDataItem.value;  
127 -  
128 UILabel *descriptionLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 80)]; 128 UILabel *descriptionLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 80)];
129 NSString *titleText = currentDataItem.textDescription; 129 NSString *titleText = currentDataItem.textDescription;
130 NSString *titleValue; 130 NSString *titleValue;
@@ -132,7 +132,7 @@ @@ -132,7 +132,7 @@
132 if (self.showAbsoluteValues) { 132 if (self.showAbsoluteValues) {
133 titleValue = [NSString stringWithFormat:@"%.0f",currentDataItem.value]; 133 titleValue = [NSString stringWithFormat:@"%.0f",currentDataItem.value];
134 }else{ 134 }else{
135 - titleValue = [NSString stringWithFormat:@"%.0f%%",currentDataItem.value/ _total * 100]; 135 + titleValue = [NSString stringWithFormat:@"%.0f%%",[self ratioForItemAtIndex:index] * 100];
136 } 136 }
137 if(!titleText || self.showOnlyValues){ 137 if(!titleText || self.showOnlyValues){
138 descriptionLabel.text = titleValue; 138 descriptionLabel.text = titleValue;
@@ -147,8 +147,7 @@ @@ -147,8 +147,7 @@
147 147
148 descriptionLabel.font = _descriptionTextFont; 148 descriptionLabel.font = _descriptionTextFont;
149 CGSize labelSize = [descriptionLabel.text sizeWithAttributes:@{NSFontAttributeName:descriptionLabel.font}]; 149 CGSize labelSize = [descriptionLabel.text sizeWithAttributes:@{NSFontAttributeName:descriptionLabel.font}];
150 - descriptionLabel.frame = CGRectMake( 150 + descriptionLabel.frame = CGRectMake(descriptionLabel.frame.origin.x, descriptionLabel.frame.origin.y,
151 - descriptionLabel.frame.origin.x, descriptionLabel.frame.origin.y,  
152 descriptionLabel.frame.size.width, labelSize.height); 151 descriptionLabel.frame.size.width, labelSize.height);
153 descriptionLabel.numberOfLines = 0; 152 descriptionLabel.numberOfLines = 0;
154 descriptionLabel.textColor = _descriptionTextColor; 153 descriptionLabel.textColor = _descriptionTextColor;
@@ -165,6 +164,22 @@ @@ -165,6 +164,22 @@
165 return self.items[index]; 164 return self.items[index];
166 } 165 }
167 166
  167 +- (CGFloat)startPercentageForItemAtIndex:(NSUInteger)index{
  168 + if(index == 0){
  169 + return 0;
  170 + }
  171 +
  172 + return [_endPercentages[index - 1] floatValue];
  173 +}
  174 +
  175 +- (CGFloat)endPercentageForItemAtIndex:(NSUInteger)index{
  176 + return [_endPercentages[index] floatValue];
  177 +}
  178 +
  179 +- (CGFloat)ratioForItemAtIndex:(NSUInteger)index{
  180 + return [self endPercentageForItemAtIndex:index] - [self startPercentageForItemAtIndex:index];
  181 +}
  182 +
168 #pragma mark private methods 183 #pragma mark private methods
169 184
170 - (CAShapeLayer *)newCircleLayerWithRadius:(CGFloat)radius 185 - (CAShapeLayer *)newCircleLayerWithRadius:(CGFloat)radius
@@ -190,13 +205,14 @@ @@ -190,13 +205,14 @@
190 circle.lineWidth = borderWidth; 205 circle.lineWidth = borderWidth;
191 circle.path = path.CGPath; 206 circle.path = path.CGPath;
192 207
193 -  
194 return circle; 208 return circle;
195 } 209 }
196 210
197 - (void)maskChart{ 211 - (void)maskChart{
198 - CAShapeLayer *maskLayer = [self newCircleLayerWithRadius:_innerCircleRadius + (_outerCircleRadius - _innerCircleRadius)/2 212 + CGFloat radius = _innerCircleRadius + (_outerCircleRadius - _innerCircleRadius) / 2;
199 - borderWidth:_outerCircleRadius - _innerCircleRadius 213 + CGFloat borderWidth = _outerCircleRadius - _innerCircleRadius;
  214 + CAShapeLayer *maskLayer = [self newCircleLayerWithRadius:radius
  215 + borderWidth:borderWidth
200 fillColor:[UIColor clearColor] 216 fillColor:[UIColor clearColor]
201 borderColor:[UIColor blackColor] 217 borderColor:[UIColor blackColor]
202 startPercentage:0 218 startPercentage:0
@@ -213,13 +229,16 @@ @@ -213,13 +229,16 @@
213 [maskLayer addAnimation:animation forKey:@"circleAnimation"]; 229 [maskLayer addAnimation:animation forKey:@"circleAnimation"];
214 } 230 }
215 231
216 -- (void)createArcAnimationForLayer:(CAShapeLayer *)layer ForKey:(NSString *)key fromValue:(NSNumber *)from toValue:(NSNumber *)to Delegate:(id)delegate 232 +- (void)createArcAnimationForLayer:(CAShapeLayer *)layer
217 -{ 233 + forKey:(NSString *)key
  234 + fromValue:(NSNumber *)from
  235 + toValue:(NSNumber *)to
  236 + delegate:(id)delegate{
218 CABasicAnimation *arcAnimation = [CABasicAnimation animationWithKeyPath:key]; 237 CABasicAnimation *arcAnimation = [CABasicAnimation animationWithKeyPath:key];
219 arcAnimation.fromValue = @0; 238 arcAnimation.fromValue = @0;
220 - [arcAnimation setToValue:to]; 239 + arcAnimation.toValue = to;
221 - [arcAnimation setDelegate:delegate]; 240 + arcAnimation.delegate = delegate;
222 - [arcAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]]; 241 + arcAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
223 [layer addAnimation:arcAnimation forKey:key]; 242 [layer addAnimation:arcAnimation forKey:key];
224 [layer setValue:to forKey:key]; 243 [layer setValue:to forKey:key];
225 } 244 }
@@ -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
@@ -156,6 +156,24 @@ @@ -156,6 +156,24 @@
156 } 156 }
157 } 157 }
158 158
  159 +- (NSArray*) getAxisMinMax:(NSArray*)xValues
  160 +{
  161 + float min = [xValues[0] floatValue];
  162 + float max = [xValues[0] floatValue];
  163 + for (NSNumber *number in xValues)
  164 + {
  165 + if ([number floatValue] > max)
  166 + max = [number floatValue];
  167 +
  168 + if ([number floatValue] < min)
  169 + min = [number floatValue];
  170 + }
  171 + NSArray *result = @[[NSNumber numberWithFloat:min], [NSNumber numberWithFloat:max]];
  172 +
  173 +
  174 + return result;
  175 +}
  176 +
159 - (void)setAxisXLabel:(NSArray *)array { 177 - (void)setAxisXLabel:(NSArray *)array {
160 if(array.count == ++_AxisX_partNumber){ 178 if(array.count == ++_AxisX_partNumber){
161 [_axisX_labels removeAllObjects]; 179 [_axisX_labels removeAllObjects];
@@ -64,7 +64,6 @@ data02.getData = ^(NSUInteger index) { @@ -64,7 +64,6 @@ data02.getData = ^(NSUInteger index) {
64 64
65 lineChart.chartData = @[data01, data02]; 65 lineChart.chartData = @[data01, data02];
66 [lineChart strokeChart]; 66 [lineChart strokeChart];
67 -  
68 ``` 67 ```
69 68
70 [![](https://dl.dropboxusercontent.com/u/1599662/bar.png)](https://dl.dropboxusercontent.com/u/1599662/bar.png) 69 [![](https://dl.dropboxusercontent.com/u/1599662/bar.png)](https://dl.dropboxusercontent.com/u/1599662/bar.png)
@@ -150,6 +149,46 @@ CGPoint end = CGPointMake(80, 45); @@ -150,6 +149,46 @@ CGPoint end = CGPointMake(80, 45);
150 scatterChart.delegate = self; 149 scatterChart.delegate = self;
151 ``` 150 ```
152 151
  152 +#### Legend
  153 +
  154 +Legend has been added to PNChart for Line and Pie Charts. Legend items position can be stacked or in series.
  155 +
  156 +[![](https://dl.dropboxusercontent.com/u/4904447/pnchart_legend_1.png)](https://dl.dropboxusercontent.com/u/4904447/pnchart_legend_1.png)
  157 +
  158 +[![](https://dl.dropboxusercontent.com/u/4904447/pnchart_legend_2.png)](https://dl.dropboxusercontent.com/u/4904447/pnchart_legend_2.png)
  159 +
  160 +```objective-c
  161 +#import "PNChart.h"
  162 +
  163 +//For Line Chart
  164 +
  165 +//Add Line Titles for the Legend
  166 +data01.dataTitle = @"Alpha";
  167 +data02.dataTitle = @"Beta Beta Beta Beta";
  168 +
  169 +//Build the legend
  170 +self.lineChart.legendStyle = PNLegendItemStyleSerial;
  171 +self.lineChart.legendFontSize = 12.0;
  172 +UIView *legend = [self.lineChart getLegendWithMaxWidth:320];
  173 +
  174 +//Move legend to the desired position and add to view
  175 +[legend setFrame:CGRectMake(100, 400, legend.frame.size.width, legend.frame.size.height)];
  176 +[self.view addSubview:legend];
  177 +
  178 +
  179 +//For Pie Chart
  180 +
  181 +//Build the legend
  182 +self.pieChart.legendStyle = PNLegendItemStyleStacked;
  183 +self.pieChart.legendFontSize = 12.0;
  184 +UIView *legend = [self.pieChart getLegendWithMaxWidth:200];
  185 +
  186 +//Move legend to the desired position and add to view
  187 +[legend setFrame:CGRectMake(130, 350, legend.frame.size.width, legend.frame.size.height)];
  188 +[self.view addSubview:legend];
  189 +```
  190 +
  191 +
153 #### Update Value 192 #### Update Value
154 193
155 Now it's easy to update value in real time 194 Now it's easy to update value in real time