├── .gitignore ├── LICENSE ├── MCGraphView.podspec ├── MCGraphView ├── MCGraphView.h └── MCGraphView.m ├── MCGraphViewDemo.gif ├── MCGraphViewDemo.mov ├── MCGraphViewDemo ├── MCGraphViewDemo.xcodeproj │ └── project.pbxproj ├── MCGraphViewDemo │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── ViewController.h │ ├── ViewController.m │ └── main.m ├── MCGraphViewDemoTests │ ├── Info.plist │ └── MCGraphViewDemoTests.m └── Podfile └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | .DS_Store 6 | build/ 7 | *.pbxuser 8 | !default.pbxuser 9 | *.mode1v3 10 | !default.mode1v3 11 | *.mode2v3 12 | !default.mode2v3 13 | *.perspectivev3 14 | !default.perspectivev3 15 | *.xcworkspace 16 | !default.xcworkspace 17 | xcuserdata 18 | profile 19 | *.moved-aside 20 | DerivedData 21 | .idea/ 22 | 23 | # CocoaPods 24 | Pods 25 | Podfile.lock 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Matthew Cheok 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /MCGraphView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'MCGraphView' 3 | s.version = '0.1.1' 4 | s.ios.deployment_target = '6.0' 5 | s.license = { :type => 'MIT', :file => 'LICENSE' } 6 | s.summary = 'A light-weight solution for displaying graphs.' 7 | s.homepage = 'https://github.com/matthewcheok/MCGraphView' 8 | s.author = { 'Matthew Cheok' => 'cheok.jz@gmail.com' } 9 | s.requires_arc = true 10 | s.source = { 11 | :git => 'https://github.com/matthewcheok/MCGraphView.git', 12 | :branch => 'master', 13 | :tag => s.version.to_s 14 | } 15 | s.source_files = 'MCGraphView/*.{h,m}' 16 | s.public_header_files = 'MCGraphView/*.h' 17 | end 18 | -------------------------------------------------------------------------------- /MCGraphView/MCGraphView.h: -------------------------------------------------------------------------------- 1 | // 2 | // MCGraphView.h 3 | // MCGraphView 4 | // 5 | // Created by Matthew Cheok on 28/11/14. 6 | // Copyright (c) 2014 Matthew Cheok. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM(NSInteger, MCGraphViewLineStyle) { 12 | MCGraphViewLineStyleDefault = 0, 13 | MCGraphViewLineStyleSmooth 14 | }; 15 | 16 | typedef NS_ENUM(NSInteger, MCGraphViewPointStyle) { 17 | MCGraphViewPointStyleNone = 0, 18 | MCGraphViewPointStyleCircle, 19 | MCGraphViewPointStyleSquare 20 | }; 21 | 22 | @class MCGraphView; 23 | @protocol MCGraphViewDelegate 24 | 25 | @optional 26 | - (NSString *)graphView:(MCGraphView *)graphView titleStringForXValue:(CGFloat)value; 27 | - (NSString *)graphView:(MCGraphView *)graphView titleStringForYValue:(CGFloat)value; 28 | 29 | - (MCGraphViewLineStyle)graphView:(MCGraphView *)graphView lineStyleForLineAtIndex:(NSInteger)index; 30 | - (MCGraphViewPointStyle)graphView:(MCGraphView *)graphView pointStyleForLineAtIndex:(NSInteger)index; 31 | 32 | - (void)graphView:(MCGraphView *)graphView willDisplayShapeLayer:(CAShapeLayer *)layer forLineAtIndex:(NSInteger)index; 33 | - (void)graphView:(MCGraphView *)graphView willDisplayLabel:(UILabel *)label forXValue:(CGFloat)value; 34 | - (void)graphView:(MCGraphView *)graphView willDisplayLabel:(UILabel *)label forYValue:(CGFloat)value; 35 | 36 | @end 37 | 38 | @interface MCGraphView : UIView 39 | 40 | @property (nonatomic, weak) IBOutlet id delegate; 41 | 42 | @property (nonatomic, strong) NSArray *lineData; // An array of arrays of NSValue (CGPoint) 43 | @property (nonatomic, strong) NSArray *lineColors; 44 | 45 | @property (nonatomic, assign) MCGraphViewLineStyle lineStyle; 46 | @property (nonatomic, assign) MCGraphViewPointStyle pointStyle; 47 | @property (nonatomic, assign) CGFloat pointRadius; 48 | 49 | @property (nonatomic, assign) CGFloat axisSpacingVertical; 50 | @property (nonatomic, assign) CGFloat axisSpacingHorizontal; 51 | @property (nonatomic, assign) NSInteger maximumLabelsVertical; 52 | @property (nonatomic, assign) NSInteger maximumLabelsHorizontal; 53 | 54 | @property (nonatomic, assign) BOOL startAtZeroVertical; 55 | @property (nonatomic, assign) BOOL startAtZeroHorizontal; 56 | @property (nonatomic, assign) UIEdgeInsets contentInset; 57 | 58 | - (void)reloadData; 59 | - (void)reloadDataAnimated:(BOOL)animated; 60 | 61 | - (void)clear; 62 | - (void)clearAnimated:(BOOL)animated; 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /MCGraphView/MCGraphView.m: -------------------------------------------------------------------------------- 1 | // 2 | // MCGraphView.m 3 | // MCGraphView 4 | // 5 | // Created by Matthew Cheok on 28/11/14. 6 | // Copyright (c) 2014 Matthew Cheok. All rights reserved. 7 | // 8 | 9 | #import "MCGraphView.h" 10 | 11 | static CGFloat kDefaultLabelWidth = 40.0; 12 | static CGFloat kDefaultLabelHeight = 12.0; 13 | 14 | static CGFloat kDefaultLineWidth = 3.0; 15 | static CGFloat kDefaultMargin = 20.0; 16 | 17 | static CGFloat kDefaultAnimationDuration = 0.3; 18 | static CGFloat kDefaultAnimationInterval = 0.3; 19 | 20 | @interface MCGraphView () 21 | 22 | @property (nonatomic, strong) NSArray *verticalAxisLabels; 23 | @property (nonatomic, strong) NSArray *horizontalAxisLabels; 24 | @property (nonatomic, strong) NSMutableArray *lineLayers; 25 | 26 | @property (nonatomic, assign) CGRect graphRect; 27 | @property (nonatomic, assign) BOOL reloading; 28 | 29 | @property (nonatomic, assign) CGFloat minX; 30 | @property (nonatomic, assign) CGFloat maxX; 31 | @property (nonatomic, assign) CGFloat minY; 32 | @property (nonatomic, assign) CGFloat maxY; 33 | 34 | @end 35 | 36 | @implementation MCGraphView 37 | 38 | - (instancetype)init { 39 | self = [super init]; 40 | if (self) { 41 | [self setup]; 42 | } 43 | return self; 44 | } 45 | 46 | - (void)awakeFromNib { 47 | [super awakeFromNib]; 48 | [self setup]; 49 | } 50 | 51 | - (void)setup { 52 | self.lineColors = @[ 53 | [UIColor colorWithRed:0.0 green:0.7407 blue:0.6107 alpha:1.0], 54 | [UIColor colorWithRed:0.1743 green:0.5907 blue:0.8691 alpha:1.0], 55 | [UIColor colorWithRed:0.9155 green:0.2931 blue:0.2075 alpha:1.0], 56 | [UIColor colorWithRed:0.9499 green:0.7742 blue:0.0 alpha:1.0] 57 | ]; 58 | self.lineLayers = [NSMutableArray array]; 59 | 60 | self.pointStyle = MCGraphViewPointStyleCircle; 61 | self.pointRadius = 2; 62 | 63 | self.contentInset = UIEdgeInsetsMake(kDefaultMargin, kDefaultMargin, kDefaultMargin, kDefaultMargin); 64 | 65 | self.axisSpacingVertical = 1; 66 | self.axisSpacingHorizontal = 1; 67 | 68 | self.maximumLabelsVertical = 5; 69 | self.maximumLabelsHorizontal = 5; 70 | } 71 | 72 | - (void)setLineData:(NSArray *)lineData { 73 | _lineData = lineData; 74 | } 75 | 76 | - (void)setLineColors:(NSArray *)lineColors { 77 | NSAssert(lineColors.count > 0, @"Need a minimum of one line color"); 78 | _lineColors = lineColors; 79 | } 80 | 81 | - (void)reloadData { 82 | [self reloadDataAnimated:NO]; 83 | } 84 | 85 | - (void)reloadDataAnimated:(BOOL)animated { 86 | if (self.reloading) { 87 | return; 88 | } 89 | 90 | if (animated) { 91 | self.reloading = YES; 92 | 93 | if (self.lineLayers.count > 0) { 94 | NSTimeInterval duration = kDefaultAnimationDuration + (self.lineLayers.count-1) * kDefaultAnimationInterval; 95 | [self _animateLinesToVisible:NO]; 96 | 97 | __weak typeof(self) weakSelf = self; 98 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, duration * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ 99 | typeof(self) strongSelf = weakSelf; 100 | [strongSelf _populateDataAnimated]; 101 | }); 102 | } 103 | else { 104 | [self _populateDataAnimated]; 105 | } 106 | } 107 | else { 108 | [self _populateData]; 109 | } 110 | } 111 | 112 | - (void)clear { 113 | [self clearAnimated:NO]; 114 | } 115 | 116 | - (void)clearAnimated:(BOOL)animated { 117 | if (self.reloading) { 118 | return; 119 | } 120 | 121 | if (animated && self.lineLayers.count > 0) { 122 | self.reloading = YES; 123 | 124 | NSTimeInterval duration = kDefaultAnimationDuration + (self.lineLayers.count-1) * kDefaultAnimationInterval; 125 | [self _animateLinesToVisible:NO]; 126 | 127 | __weak typeof(self) weakSelf = self; 128 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, duration * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ 129 | typeof(self) strongSelf = weakSelf; 130 | [strongSelf _reset]; 131 | }); 132 | } 133 | else { 134 | [self _reset]; 135 | } 136 | } 137 | 138 | // MARK: Private 139 | 140 | - (CGPoint)_transformedPoint:(CGPoint)point { 141 | CGFloat x = (point.x - self.minX) / (self.maxX - self.minX); 142 | CGFloat y = (point.y - self.minY) / (self.maxY - self.minY); 143 | return CGPointMake(CGRectGetMinX(self.graphRect) + x * CGRectGetWidth(self.graphRect), CGRectGetMaxY(self.graphRect) - y * CGRectGetHeight(self.graphRect)); 144 | } 145 | 146 | - (void)_populateData { 147 | [self _reset]; 148 | 149 | if ([self.lineData count] == 0) { 150 | return; 151 | } 152 | 153 | [self _computeAxes]; 154 | [self _placeVerticalAxisLabels]; 155 | [self _placeHorizontalAxisLabels]; 156 | 157 | for (int i = 0; i < self.lineData.count; i++) { 158 | [self _drawLineAtIndex:i]; 159 | [self _plotLineAtIndex:i]; 160 | } 161 | } 162 | 163 | - (void)_populateDataAnimated { 164 | [self _populateData]; 165 | [self _animateLinesToVisible:YES]; 166 | 167 | NSTimeInterval duration = kDefaultAnimationDuration + (self.lineLayers.count-1) * kDefaultAnimationInterval; 168 | 169 | __weak typeof(self) weakSelf = self; 170 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, duration * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ 171 | typeof(self) strongSelf = weakSelf; 172 | strongSelf.reloading = NO; 173 | }); 174 | } 175 | 176 | - (void)_reset { 177 | for (UILabel *label in self.verticalAxisLabels) { 178 | [label removeFromSuperview]; 179 | } 180 | self.verticalAxisLabels = nil; 181 | 182 | for (UILabel *label in self.horizontalAxisLabels) { 183 | [label removeFromSuperview]; 184 | } 185 | self.horizontalAxisLabels = nil; 186 | 187 | self.layer.sublayers = nil; 188 | [self.lineLayers removeAllObjects]; 189 | } 190 | 191 | - (void)_computeAxes { 192 | self.minX = self.startAtZeroHorizontal ? 0 : MAXFLOAT; 193 | self.minY = self.startAtZeroVertical ? 0 : MAXFLOAT; 194 | 195 | self.maxX = self.startAtZeroHorizontal ? 0 : -MAXFLOAT; 196 | self.maxY = self.startAtZeroVertical ? 0 : -MAXFLOAT; 197 | 198 | for (NSArray *line in self.lineData) { 199 | for (NSValue *value in line) { 200 | CGPoint point = [value CGPointValue]; 201 | if (!self.startAtZeroHorizontal && point.x < self.minX) { 202 | self.minX = point.x; 203 | } 204 | 205 | if (point.x > self.maxX) { 206 | self.maxX = point.x; 207 | } 208 | 209 | if (!self.startAtZeroVertical && point.y < self.minY) { 210 | self.minY = point.y; 211 | } 212 | 213 | if (point.y > self.maxY) { 214 | self.maxY = point.y; 215 | } 216 | } 217 | } 218 | 219 | // set minimum region size 220 | if (fabsf(self.maxX - self.minX) < self.axisSpacingHorizontal) { 221 | self.minX -= self.axisSpacingHorizontal; 222 | self.maxX += self.axisSpacingHorizontal; 223 | } 224 | 225 | if (fabsf(self.maxY - self.minY) < self.axisSpacingVertical) { 226 | self.minY -= self.axisSpacingVertical; 227 | self.maxY += self.axisSpacingVertical; 228 | } 229 | else { 230 | // apply padding to vertical axis 231 | CGFloat difference = self.maxY - self.minY; 232 | CGFloat padding = difference * 0.2; 233 | self.minY -= padding / 2; 234 | self.maxY += padding / 2; 235 | } 236 | 237 | // compute drawing region 238 | CGRect rect = self.bounds; 239 | rect.origin.x += self.contentInset.left; 240 | rect.origin.y += self.contentInset.top; 241 | rect.size.width -= self.contentInset.left + self.contentInset.right; 242 | rect.size.height -= self.contentInset.top + self.contentInset.bottom; 243 | 244 | // make space for labels on vertical axis 245 | rect.origin.x += kDefaultLabelWidth + kDefaultMargin; 246 | rect.size.width -= kDefaultLabelWidth + kDefaultMargin; 247 | 248 | // make space for labels on horizontal axis 249 | rect.size.height -= kDefaultLabelHeight + kDefaultMargin; 250 | 251 | self.graphRect = rect; 252 | } 253 | 254 | - (void)_placeVerticalAxisLabels { 255 | for (UILabel *label in self.verticalAxisLabels) { 256 | [label removeFromSuperview]; 257 | } 258 | self.verticalAxisLabels = nil; 259 | 260 | __weak typeof(self) weakSelf = self; 261 | 262 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 263 | typeof(self) strongSelf = weakSelf; 264 | CGFloat jump = strongSelf.axisSpacingVertical; 265 | while ((strongSelf.maxY - strongSelf.minY) / jump > strongSelf.maximumLabelsVertical) { 266 | jump += strongSelf.axisSpacingVertical; 267 | } 268 | 269 | dispatch_async(dispatch_get_main_queue(), ^{ 270 | typeof(self) strongSelf = weakSelf; 271 | NSMutableArray *labels = [NSMutableArray array]; 272 | 273 | CGFloat value = 0; 274 | if (!strongSelf.startAtZeroVertical) { 275 | value = ceil(strongSelf.minY / strongSelf.axisSpacingVertical) * strongSelf.axisSpacingVertical; 276 | } 277 | 278 | while (value <= strongSelf.maxY) { 279 | UILabel *label = [strongSelf _label]; 280 | label.textAlignment = NSTextAlignmentRight; 281 | 282 | NSString *title = nil; 283 | if ([strongSelf.delegate respondsToSelector:@selector(graphView:titleStringForYValue:)]) { 284 | title = [strongSelf.delegate graphView:strongSelf titleStringForYValue:value]; 285 | } 286 | else { 287 | title = [NSString stringWithFormat:@"%.2f", value]; 288 | } 289 | 290 | label.text = title; 291 | 292 | CGPoint center = [self _transformedPoint:CGPointMake(strongSelf.minX, value)]; 293 | center.x -= kDefaultLabelWidth / 2 + kDefaultMargin; 294 | label.center = center; 295 | 296 | if ([strongSelf.delegate respondsToSelector:@selector(graphView:willDisplayLabel:forYValue:)]) { 297 | [strongSelf.delegate graphView:self willDisplayLabel:label forYValue:value]; 298 | } 299 | [strongSelf addSubview:label]; 300 | 301 | [labels addObject:label]; 302 | value += jump; 303 | } 304 | 305 | strongSelf.verticalAxisLabels = [labels copy]; 306 | }); 307 | }); 308 | } 309 | 310 | - (void)_placeHorizontalAxisLabels { 311 | for (UILabel *label in self.horizontalAxisLabels) { 312 | [label removeFromSuperview]; 313 | } 314 | self.horizontalAxisLabels = nil; 315 | 316 | __weak typeof(self) weakSelf = self; 317 | 318 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 319 | typeof(self) strongSelf = weakSelf; 320 | CGFloat jump = strongSelf.axisSpacingHorizontal; 321 | while ((strongSelf.maxX - strongSelf.minX) / jump > strongSelf.maximumLabelsHorizontal) { 322 | jump += strongSelf.axisSpacingHorizontal; 323 | } 324 | 325 | dispatch_async(dispatch_get_main_queue(), ^{ 326 | typeof(self) strongSelf = weakSelf; 327 | NSMutableArray *labels = [NSMutableArray array]; 328 | 329 | CGFloat value = 0; 330 | if (!strongSelf.startAtZeroHorizontal) { 331 | value = ceil(strongSelf.minX / strongSelf.axisSpacingHorizontal) * strongSelf.axisSpacingHorizontal; 332 | } 333 | 334 | while (value <= strongSelf.maxX) { 335 | UILabel *label = [strongSelf _label]; 336 | label.numberOfLines = 0; 337 | 338 | NSString *title = nil; 339 | if ([strongSelf.delegate respondsToSelector:@selector(graphView:titleStringForXValue:)]) { 340 | title = [strongSelf.delegate graphView:self titleStringForXValue:value]; 341 | } 342 | else { 343 | title = [NSString stringWithFormat:@"%.2f", value]; 344 | } 345 | 346 | label.text = title; 347 | 348 | CGPoint center = [self _transformedPoint:CGPointMake(value, self.minY)]; 349 | center.y += kDefaultLabelHeight / 2 + kDefaultMargin; 350 | 351 | [label sizeToFit]; 352 | label.center = center; 353 | 354 | if ([strongSelf.delegate respondsToSelector:@selector(graphView:willDisplayLabel:forXValue:)]) { 355 | [strongSelf.delegate graphView:self willDisplayLabel:label forXValue:value]; 356 | } 357 | [strongSelf addSubview:label]; 358 | 359 | [labels addObject:label]; 360 | value += jump; 361 | } 362 | 363 | strongSelf.horizontalAxisLabels = [labels copy]; 364 | }); 365 | }); 366 | } 367 | 368 | - (void)_animateLinesToVisible:(BOOL)visible { 369 | for (int i = 0; i < self.lineLayers.count; i++) { 370 | CAShapeLayer *layer = self.lineLayers[i]; 371 | CABasicAnimation *animation = nil; 372 | 373 | if (visible) { 374 | animation = [self _animationWithKeyPath:@"strokeEnd"]; 375 | layer.strokeEnd = 1; 376 | } 377 | else { 378 | animation = [self _animationWithKeyPath:@"strokeStart"]; 379 | layer.strokeStart = 1; 380 | } 381 | 382 | animation.duration += kDefaultAnimationInterval * i; 383 | [layer addAnimation:animation forKey:@"strokeAnimation"]; 384 | } 385 | } 386 | 387 | - (void)_plotLineAtIndex:(NSInteger)index { 388 | MCGraphViewPointStyle style = self.pointStyle; 389 | if ([self.delegate respondsToSelector:@selector(graphView:pointStyleForLineAtIndex:)]) { 390 | style = [self.delegate graphView:self pointStyleForLineAtIndex:index]; 391 | } 392 | 393 | if (style == MCGraphViewPointStyleNone) { 394 | return; 395 | } 396 | 397 | NSArray *points = self.lineData[index]; 398 | 399 | for (int i = 0; i < [points count]; i++) { 400 | UIBezierPath *path = nil; 401 | CAShapeLayer *layer = [self _strokeLayer]; 402 | 403 | UIColor *color = self.lineColors[index % self.lineColors.count]; 404 | layer.strokeColor = [color CGColor]; 405 | layer.fillColor = [color CGColor]; 406 | [self.layer addSublayer:layer]; 407 | 408 | CGPoint p1 = [self _transformedPoint:[points[i] CGPointValue]]; 409 | 410 | if (style == MCGraphViewPointStyleCircle) { 411 | path = [UIBezierPath bezierPathWithArcCenter:p1 radius:self.pointRadius startAngle:0 endAngle:2 * M_PI clockwise:YES]; 412 | } 413 | else if (style == MCGraphViewPointStyleSquare) { 414 | path = [UIBezierPath bezierPathWithRect:CGRectMake(p1.x - self.pointRadius, p1.y - self.pointRadius, self.pointRadius * 2, self.pointRadius * 2)]; 415 | } 416 | layer.path = path.CGPath; 417 | } 418 | } 419 | 420 | - (void)_drawLineAtIndex:(NSInteger)index { 421 | // http://stackoverflow.com/questions/19599266/invalid-context-0x0-under-ios-7-0-and-system-degradation 422 | UIGraphicsBeginImageContext(self.frame.size); 423 | 424 | UIBezierPath *path = [self _bezierPath]; 425 | CAShapeLayer *layer = [self _strokeLayer]; 426 | 427 | if ([self.delegate respondsToSelector:@selector(graphView:willDisplayShapeLayer:forLineAtIndex:)]) { 428 | [self.delegate graphView:self willDisplayShapeLayer:layer forLineAtIndex:index]; 429 | } 430 | 431 | UIColor *color = self.lineColors[index % self.lineColors.count]; 432 | layer.strokeColor = [color CGColor]; 433 | [self.layer addSublayer:layer]; 434 | 435 | MCGraphViewLineStyle lineStyle = self.lineStyle; 436 | if ([self.delegate respondsToSelector:@selector(graphView:lineStyleForLineAtIndex:)]) { 437 | lineStyle = [self.delegate graphView:self lineStyleForLineAtIndex:index]; 438 | } 439 | 440 | NSArray *points = self.lineData[index]; 441 | 442 | for (int i = 0; i < [points count] - 1; i++) { 443 | CGPoint p1 = [self _transformedPoint:[points[i] CGPointValue]]; 444 | CGPoint p2 = [self _transformedPoint:[points[i + 1] CGPointValue]]; 445 | 446 | [path moveToPoint:p1]; 447 | 448 | if (lineStyle == MCGraphViewLineStyleSmooth) { 449 | CGFloat tensionBezier1 = 0.3; 450 | CGFloat tensionBezier2 = 0.3; 451 | CGPoint p0 = p1; 452 | CGPoint p3 = p2; 453 | 454 | if (i > 0) { // Exception for first line because there is no previous point 455 | p0 = [self _transformedPoint:[points[i - 1] CGPointValue]]; 456 | } 457 | else { 458 | tensionBezier1 = 0; 459 | } 460 | 461 | if (i < [points count] - 2) { // Exception for last line because there is no next point 462 | p3 = [self _transformedPoint:[points[i + 2] CGPointValue]]; 463 | } 464 | else { 465 | tensionBezier2 = 0; 466 | } 467 | 468 | // First control point 469 | CGPoint CP1 = CGPointMake(p1.x + (p2.x - p1.x) / 3, 470 | p1.y - (p1.y - p2.y) / 3 - (p0.y - p1.y) * tensionBezier1); 471 | 472 | // Second control point 473 | CGPoint CP2 = CGPointMake(p1.x + 2 * (p2.x - p1.x) / 3, 474 | (p1.y - 2 * (p1.y - p2.y) / 3) + (p2.y - p3.y) * tensionBezier2); 475 | 476 | 477 | 478 | [path addCurveToPoint:p2 controlPoint1:CP1 controlPoint2:CP2]; 479 | } 480 | else { 481 | [path addLineToPoint:p2]; 482 | } 483 | } 484 | 485 | layer.path = path.CGPath; 486 | [self.lineLayers addObject:layer]; 487 | 488 | UIGraphicsEndImageContext(); 489 | } 490 | 491 | - (UILabel *)_label { 492 | CGRect frame = CGRectMake(0, 0, kDefaultLabelWidth, kDefaultLabelHeight); 493 | UILabel *label = [[UILabel alloc] initWithFrame:frame]; 494 | label.font = [UIFont boldSystemFontOfSize:12]; 495 | label.textAlignment = NSTextAlignmentCenter; 496 | label.textColor = [UIColor lightGrayColor]; 497 | return label; 498 | } 499 | 500 | - (UIBezierPath *)_bezierPath { 501 | UIBezierPath *path = [UIBezierPath bezierPath]; 502 | path.lineCapStyle = kCGLineCapRound; 503 | path.lineJoinStyle = kCGLineJoinRound; 504 | path.lineWidth = kDefaultLineWidth; 505 | return path; 506 | } 507 | 508 | - (CAShapeLayer *)_strokeLayer { 509 | CAShapeLayer *layer = [CAShapeLayer layer]; 510 | layer.fillColor = [[UIColor blackColor] CGColor]; 511 | layer.lineCap = kCALineCapRound; 512 | layer.lineJoin = kCALineJoinRound; 513 | layer.lineWidth = kDefaultLineWidth; 514 | layer.fillColor = [[UIColor clearColor] CGColor]; 515 | layer.strokeColor = [[UIColor redColor] CGColor]; 516 | layer.strokeEnd = 1; 517 | return layer; 518 | } 519 | 520 | - (CABasicAnimation *)_animationWithKeyPath:(NSString *)keyPath { 521 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:keyPath]; 522 | animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 523 | animation.duration = kDefaultAnimationDuration; 524 | animation.fromValue = @(0); 525 | animation.toValue = @(1); 526 | return animation; 527 | } 528 | 529 | @end 530 | -------------------------------------------------------------------------------- /MCGraphViewDemo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewcheok/MCGraphView/37730571910859565433e08cd71d13fb6dfe4c11/MCGraphViewDemo.gif -------------------------------------------------------------------------------- /MCGraphViewDemo.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewcheok/MCGraphView/37730571910859565433e08cd71d13fb6dfe4c11/MCGraphViewDemo.mov -------------------------------------------------------------------------------- /MCGraphViewDemo/MCGraphViewDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | archiveVersion 6 | 1 7 | classes 8 | 9 | objectVersion 10 | 46 11 | objects 12 | 13 | 5191B2F45028E9B06C6EF44D 14 | 15 | buildActionMask 16 | 2147483647 17 | files 18 | 19 | inputPaths 20 | 21 | isa 22 | PBXShellScriptBuildPhase 23 | name 24 | Check Pods Manifest.lock 25 | outputPaths 26 | 27 | runOnlyForDeploymentPostprocessing 28 | 0 29 | shellPath 30 | /bin/sh 31 | shellScript 32 | diff "${PODS_ROOT}/../Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null 33 | if [[ $? != 0 ]] ; then 34 | cat << EOM 35 | error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation. 36 | EOM 37 | exit 1 38 | fi 39 | 40 | showEnvVarsInLog 41 | 0 42 | 43 | 71FDC134D96DC7F2947D9158 44 | 45 | includeInIndex 46 | 1 47 | isa 48 | PBXFileReference 49 | lastKnownFileType 50 | text.xcconfig 51 | name 52 | Pods.release.xcconfig 53 | path 54 | Pods/Target Support Files/Pods/Pods.release.xcconfig 55 | sourceTree 56 | <group> 57 | 58 | 725A67A7D2C70DAA000F7CFA 59 | 60 | includeInIndex 61 | 1 62 | isa 63 | PBXFileReference 64 | lastKnownFileType 65 | text.xcconfig 66 | name 67 | Pods.debug.xcconfig 68 | path 69 | Pods/Target Support Files/Pods/Pods.debug.xcconfig 70 | sourceTree 71 | <group> 72 | 73 | 7D51C8A3E5851707DAE92AEE 74 | 75 | explicitFileType 76 | archive.ar 77 | includeInIndex 78 | 0 79 | isa 80 | PBXFileReference 81 | path 82 | libPods.a 83 | sourceTree 84 | BUILT_PRODUCTS_DIR 85 | 86 | 9E4D88751A28AF7A001660AF 87 | 88 | children 89 | 90 | 9E4D88801A28AF7A001660AF 91 | 9E4D889A1A28AF7A001660AF 92 | 9E4D887F1A28AF7A001660AF 93 | A6F93E6A383CB558D05964BF 94 | FCDE11C8F051296BA6C46F24 95 | 96 | isa 97 | PBXGroup 98 | sourceTree 99 | <group> 100 | 101 | 9E4D88761A28AF7A001660AF 102 | 103 | attributes 104 | 105 | LastUpgradeCheck 106 | 0610 107 | ORGANIZATIONNAME 108 | Matthew Cheok 109 | TargetAttributes 110 | 111 | 9E4D887D1A28AF7A001660AF 112 | 113 | CreatedOnToolsVersion 114 | 6.1 115 | 116 | 9E4D88961A28AF7A001660AF 117 | 118 | CreatedOnToolsVersion 119 | 6.1 120 | TestTargetID 121 | 9E4D887D1A28AF7A001660AF 122 | 123 | 124 | 125 | buildConfigurationList 126 | 9E4D88791A28AF7A001660AF 127 | compatibilityVersion 128 | Xcode 3.2 129 | developmentRegion 130 | English 131 | hasScannedForEncodings 132 | 0 133 | isa 134 | PBXProject 135 | knownRegions 136 | 137 | en 138 | Base 139 | 140 | mainGroup 141 | 9E4D88751A28AF7A001660AF 142 | productRefGroup 143 | 9E4D887F1A28AF7A001660AF 144 | projectDirPath 145 | 146 | projectReferences 147 | 148 | projectRoot 149 | 150 | targets 151 | 152 | 9E4D887D1A28AF7A001660AF 153 | 9E4D88961A28AF7A001660AF 154 | 155 | 156 | 9E4D88791A28AF7A001660AF 157 | 158 | buildConfigurations 159 | 160 | 9E4D889F1A28AF7A001660AF 161 | 9E4D88A01A28AF7A001660AF 162 | 163 | defaultConfigurationIsVisible 164 | 0 165 | defaultConfigurationName 166 | Release 167 | isa 168 | XCConfigurationList 169 | 170 | 9E4D887A1A28AF7A001660AF 171 | 172 | buildActionMask 173 | 2147483647 174 | files 175 | 176 | 9E4D888A1A28AF7A001660AF 177 | 9E4D88871A28AF7A001660AF 178 | 9E4D88841A28AF7A001660AF 179 | 180 | isa 181 | PBXSourcesBuildPhase 182 | runOnlyForDeploymentPostprocessing 183 | 0 184 | 185 | 9E4D887B1A28AF7A001660AF 186 | 187 | buildActionMask 188 | 2147483647 189 | files 190 | 191 | EF68330B2411175BA79D2D0C 192 | 193 | isa 194 | PBXFrameworksBuildPhase 195 | runOnlyForDeploymentPostprocessing 196 | 0 197 | 198 | 9E4D887C1A28AF7A001660AF 199 | 200 | buildActionMask 201 | 2147483647 202 | files 203 | 204 | 9E4D888D1A28AF7A001660AF 205 | 9E4D88921A28AF7A001660AF 206 | 9E4D888F1A28AF7A001660AF 207 | 208 | isa 209 | PBXResourcesBuildPhase 210 | runOnlyForDeploymentPostprocessing 211 | 0 212 | 213 | 9E4D887D1A28AF7A001660AF 214 | 215 | buildConfigurationList 216 | 9E4D88A11A28AF7A001660AF 217 | buildPhases 218 | 219 | 5191B2F45028E9B06C6EF44D 220 | 9E4D887A1A28AF7A001660AF 221 | 9E4D887B1A28AF7A001660AF 222 | 9E4D887C1A28AF7A001660AF 223 | C460D89EB4636E5E0F802047 224 | 225 | buildRules 226 | 227 | dependencies 228 | 229 | isa 230 | PBXNativeTarget 231 | name 232 | MCGraphViewDemo 233 | productName 234 | MCGraphViewDemo 235 | productReference 236 | 9E4D887E1A28AF7A001660AF 237 | productType 238 | com.apple.product-type.application 239 | 240 | 9E4D887E1A28AF7A001660AF 241 | 242 | explicitFileType 243 | wrapper.application 244 | includeInIndex 245 | 0 246 | isa 247 | PBXFileReference 248 | path 249 | MCGraphViewDemo.app 250 | sourceTree 251 | BUILT_PRODUCTS_DIR 252 | 253 | 9E4D887F1A28AF7A001660AF 254 | 255 | children 256 | 257 | 9E4D887E1A28AF7A001660AF 258 | 9E4D88971A28AF7A001660AF 259 | 260 | isa 261 | PBXGroup 262 | name 263 | Products 264 | sourceTree 265 | <group> 266 | 267 | 9E4D88801A28AF7A001660AF 268 | 269 | children 270 | 271 | 9E4D88851A28AF7A001660AF 272 | 9E4D88861A28AF7A001660AF 273 | 9E4D88881A28AF7A001660AF 274 | 9E4D88891A28AF7A001660AF 275 | 9E4D888B1A28AF7A001660AF 276 | 9E4D888E1A28AF7A001660AF 277 | 9E4D88901A28AF7A001660AF 278 | 9E4D88811A28AF7A001660AF 279 | 280 | isa 281 | PBXGroup 282 | path 283 | MCGraphViewDemo 284 | sourceTree 285 | <group> 286 | 287 | 9E4D88811A28AF7A001660AF 288 | 289 | children 290 | 291 | 9E4D88821A28AF7A001660AF 292 | 9E4D88831A28AF7A001660AF 293 | 294 | isa 295 | PBXGroup 296 | name 297 | Supporting Files 298 | sourceTree 299 | <group> 300 | 301 | 9E4D88821A28AF7A001660AF 302 | 303 | isa 304 | PBXFileReference 305 | lastKnownFileType 306 | text.plist.xml 307 | path 308 | Info.plist 309 | sourceTree 310 | <group> 311 | 312 | 9E4D88831A28AF7A001660AF 313 | 314 | isa 315 | PBXFileReference 316 | lastKnownFileType 317 | sourcecode.c.objc 318 | path 319 | main.m 320 | sourceTree 321 | <group> 322 | 323 | 9E4D88841A28AF7A001660AF 324 | 325 | fileRef 326 | 9E4D88831A28AF7A001660AF 327 | isa 328 | PBXBuildFile 329 | 330 | 9E4D88851A28AF7A001660AF 331 | 332 | isa 333 | PBXFileReference 334 | lastKnownFileType 335 | sourcecode.c.h 336 | path 337 | AppDelegate.h 338 | sourceTree 339 | <group> 340 | 341 | 9E4D88861A28AF7A001660AF 342 | 343 | isa 344 | PBXFileReference 345 | lastKnownFileType 346 | sourcecode.c.objc 347 | path 348 | AppDelegate.m 349 | sourceTree 350 | <group> 351 | 352 | 9E4D88871A28AF7A001660AF 353 | 354 | fileRef 355 | 9E4D88861A28AF7A001660AF 356 | isa 357 | PBXBuildFile 358 | 359 | 9E4D88881A28AF7A001660AF 360 | 361 | isa 362 | PBXFileReference 363 | lastKnownFileType 364 | sourcecode.c.h 365 | path 366 | ViewController.h 367 | sourceTree 368 | <group> 369 | 370 | 9E4D88891A28AF7A001660AF 371 | 372 | isa 373 | PBXFileReference 374 | lastKnownFileType 375 | sourcecode.c.objc 376 | path 377 | ViewController.m 378 | sourceTree 379 | <group> 380 | 381 | 9E4D888A1A28AF7A001660AF 382 | 383 | fileRef 384 | 9E4D88891A28AF7A001660AF 385 | isa 386 | PBXBuildFile 387 | 388 | 9E4D888B1A28AF7A001660AF 389 | 390 | children 391 | 392 | 9E4D888C1A28AF7A001660AF 393 | 394 | isa 395 | PBXVariantGroup 396 | name 397 | Main.storyboard 398 | sourceTree 399 | <group> 400 | 401 | 9E4D888C1A28AF7A001660AF 402 | 403 | isa 404 | PBXFileReference 405 | lastKnownFileType 406 | file.storyboard 407 | name 408 | Base 409 | path 410 | Base.lproj/Main.storyboard 411 | sourceTree 412 | <group> 413 | 414 | 9E4D888D1A28AF7A001660AF 415 | 416 | fileRef 417 | 9E4D888B1A28AF7A001660AF 418 | isa 419 | PBXBuildFile 420 | 421 | 9E4D888E1A28AF7A001660AF 422 | 423 | isa 424 | PBXFileReference 425 | lastKnownFileType 426 | folder.assetcatalog 427 | path 428 | Images.xcassets 429 | sourceTree 430 | <group> 431 | 432 | 9E4D888F1A28AF7A001660AF 433 | 434 | fileRef 435 | 9E4D888E1A28AF7A001660AF 436 | isa 437 | PBXBuildFile 438 | 439 | 9E4D88901A28AF7A001660AF 440 | 441 | children 442 | 443 | 9E4D88911A28AF7A001660AF 444 | 445 | isa 446 | PBXVariantGroup 447 | name 448 | LaunchScreen.xib 449 | sourceTree 450 | <group> 451 | 452 | 9E4D88911A28AF7A001660AF 453 | 454 | isa 455 | PBXFileReference 456 | lastKnownFileType 457 | file.xib 458 | name 459 | Base 460 | path 461 | Base.lproj/LaunchScreen.xib 462 | sourceTree 463 | <group> 464 | 465 | 9E4D88921A28AF7A001660AF 466 | 467 | fileRef 468 | 9E4D88901A28AF7A001660AF 469 | isa 470 | PBXBuildFile 471 | 472 | 9E4D88931A28AF7A001660AF 473 | 474 | buildActionMask 475 | 2147483647 476 | files 477 | 478 | 9E4D889E1A28AF7A001660AF 479 | 480 | isa 481 | PBXSourcesBuildPhase 482 | runOnlyForDeploymentPostprocessing 483 | 0 484 | 485 | 9E4D88941A28AF7A001660AF 486 | 487 | buildActionMask 488 | 2147483647 489 | files 490 | 491 | isa 492 | PBXFrameworksBuildPhase 493 | runOnlyForDeploymentPostprocessing 494 | 0 495 | 496 | 9E4D88951A28AF7A001660AF 497 | 498 | buildActionMask 499 | 2147483647 500 | files 501 | 502 | isa 503 | PBXResourcesBuildPhase 504 | runOnlyForDeploymentPostprocessing 505 | 0 506 | 507 | 9E4D88961A28AF7A001660AF 508 | 509 | buildConfigurationList 510 | 9E4D88A41A28AF7A001660AF 511 | buildPhases 512 | 513 | 9E4D88931A28AF7A001660AF 514 | 9E4D88941A28AF7A001660AF 515 | 9E4D88951A28AF7A001660AF 516 | 517 | buildRules 518 | 519 | dependencies 520 | 521 | 9E4D88991A28AF7A001660AF 522 | 523 | isa 524 | PBXNativeTarget 525 | name 526 | MCGraphViewDemoTests 527 | productName 528 | MCGraphViewDemoTests 529 | productReference 530 | 9E4D88971A28AF7A001660AF 531 | productType 532 | com.apple.product-type.bundle.unit-test 533 | 534 | 9E4D88971A28AF7A001660AF 535 | 536 | explicitFileType 537 | wrapper.cfbundle 538 | includeInIndex 539 | 0 540 | isa 541 | PBXFileReference 542 | path 543 | MCGraphViewDemoTests.xctest 544 | sourceTree 545 | BUILT_PRODUCTS_DIR 546 | 547 | 9E4D88981A28AF7A001660AF 548 | 549 | containerPortal 550 | 9E4D88761A28AF7A001660AF 551 | isa 552 | PBXContainerItemProxy 553 | proxyType 554 | 1 555 | remoteGlobalIDString 556 | 9E4D887D1A28AF7A001660AF 557 | remoteInfo 558 | MCGraphViewDemo 559 | 560 | 9E4D88991A28AF7A001660AF 561 | 562 | isa 563 | PBXTargetDependency 564 | target 565 | 9E4D887D1A28AF7A001660AF 566 | targetProxy 567 | 9E4D88981A28AF7A001660AF 568 | 569 | 9E4D889A1A28AF7A001660AF 570 | 571 | children 572 | 573 | 9E4D889D1A28AF7A001660AF 574 | 9E4D889B1A28AF7A001660AF 575 | 576 | isa 577 | PBXGroup 578 | path 579 | MCGraphViewDemoTests 580 | sourceTree 581 | <group> 582 | 583 | 9E4D889B1A28AF7A001660AF 584 | 585 | children 586 | 587 | 9E4D889C1A28AF7A001660AF 588 | 589 | isa 590 | PBXGroup 591 | name 592 | Supporting Files 593 | sourceTree 594 | <group> 595 | 596 | 9E4D889C1A28AF7A001660AF 597 | 598 | isa 599 | PBXFileReference 600 | lastKnownFileType 601 | text.plist.xml 602 | path 603 | Info.plist 604 | sourceTree 605 | <group> 606 | 607 | 9E4D889D1A28AF7A001660AF 608 | 609 | isa 610 | PBXFileReference 611 | lastKnownFileType 612 | sourcecode.c.objc 613 | path 614 | MCGraphViewDemoTests.m 615 | sourceTree 616 | <group> 617 | 618 | 9E4D889E1A28AF7A001660AF 619 | 620 | fileRef 621 | 9E4D889D1A28AF7A001660AF 622 | isa 623 | PBXBuildFile 624 | 625 | 9E4D889F1A28AF7A001660AF 626 | 627 | buildSettings 628 | 629 | ALWAYS_SEARCH_USER_PATHS 630 | NO 631 | CLANG_CXX_LANGUAGE_STANDARD 632 | gnu++0x 633 | CLANG_CXX_LIBRARY 634 | libc++ 635 | CLANG_ENABLE_MODULES 636 | YES 637 | CLANG_ENABLE_OBJC_ARC 638 | YES 639 | CLANG_WARN_BOOL_CONVERSION 640 | YES 641 | CLANG_WARN_CONSTANT_CONVERSION 642 | YES 643 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE 644 | YES_ERROR 645 | CLANG_WARN_EMPTY_BODY 646 | YES 647 | CLANG_WARN_ENUM_CONVERSION 648 | YES 649 | CLANG_WARN_INT_CONVERSION 650 | YES 651 | CLANG_WARN_OBJC_ROOT_CLASS 652 | YES_ERROR 653 | CLANG_WARN_UNREACHABLE_CODE 654 | YES 655 | CLANG_WARN__DUPLICATE_METHOD_MATCH 656 | YES 657 | CODE_SIGN_IDENTITY[sdk=iphoneos*] 658 | iPhone Developer 659 | COPY_PHASE_STRIP 660 | NO 661 | ENABLE_STRICT_OBJC_MSGSEND 662 | YES 663 | GCC_C_LANGUAGE_STANDARD 664 | gnu99 665 | GCC_DYNAMIC_NO_PIC 666 | NO 667 | GCC_OPTIMIZATION_LEVEL 668 | 0 669 | GCC_PREPROCESSOR_DEFINITIONS 670 | 671 | DEBUG=1 672 | $(inherited) 673 | 674 | GCC_SYMBOLS_PRIVATE_EXTERN 675 | NO 676 | GCC_WARN_64_TO_32_BIT_CONVERSION 677 | YES 678 | GCC_WARN_ABOUT_RETURN_TYPE 679 | YES_ERROR 680 | GCC_WARN_UNDECLARED_SELECTOR 681 | YES 682 | GCC_WARN_UNINITIALIZED_AUTOS 683 | YES_AGGRESSIVE 684 | GCC_WARN_UNUSED_FUNCTION 685 | YES 686 | GCC_WARN_UNUSED_VARIABLE 687 | YES 688 | IPHONEOS_DEPLOYMENT_TARGET 689 | 8.1 690 | MTL_ENABLE_DEBUG_INFO 691 | YES 692 | ONLY_ACTIVE_ARCH 693 | YES 694 | SDKROOT 695 | iphoneos 696 | 697 | isa 698 | XCBuildConfiguration 699 | name 700 | Debug 701 | 702 | 9E4D88A01A28AF7A001660AF 703 | 704 | buildSettings 705 | 706 | ALWAYS_SEARCH_USER_PATHS 707 | NO 708 | CLANG_CXX_LANGUAGE_STANDARD 709 | gnu++0x 710 | CLANG_CXX_LIBRARY 711 | libc++ 712 | CLANG_ENABLE_MODULES 713 | YES 714 | CLANG_ENABLE_OBJC_ARC 715 | YES 716 | CLANG_WARN_BOOL_CONVERSION 717 | YES 718 | CLANG_WARN_CONSTANT_CONVERSION 719 | YES 720 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE 721 | YES_ERROR 722 | CLANG_WARN_EMPTY_BODY 723 | YES 724 | CLANG_WARN_ENUM_CONVERSION 725 | YES 726 | CLANG_WARN_INT_CONVERSION 727 | YES 728 | CLANG_WARN_OBJC_ROOT_CLASS 729 | YES_ERROR 730 | CLANG_WARN_UNREACHABLE_CODE 731 | YES 732 | CLANG_WARN__DUPLICATE_METHOD_MATCH 733 | YES 734 | CODE_SIGN_IDENTITY[sdk=iphoneos*] 735 | iPhone Developer 736 | COPY_PHASE_STRIP 737 | YES 738 | ENABLE_NS_ASSERTIONS 739 | NO 740 | ENABLE_STRICT_OBJC_MSGSEND 741 | YES 742 | GCC_C_LANGUAGE_STANDARD 743 | gnu99 744 | GCC_WARN_64_TO_32_BIT_CONVERSION 745 | YES 746 | GCC_WARN_ABOUT_RETURN_TYPE 747 | YES_ERROR 748 | GCC_WARN_UNDECLARED_SELECTOR 749 | YES 750 | GCC_WARN_UNINITIALIZED_AUTOS 751 | YES_AGGRESSIVE 752 | GCC_WARN_UNUSED_FUNCTION 753 | YES 754 | GCC_WARN_UNUSED_VARIABLE 755 | YES 756 | IPHONEOS_DEPLOYMENT_TARGET 757 | 8.1 758 | MTL_ENABLE_DEBUG_INFO 759 | NO 760 | SDKROOT 761 | iphoneos 762 | VALIDATE_PRODUCT 763 | YES 764 | 765 | isa 766 | XCBuildConfiguration 767 | name 768 | Release 769 | 770 | 9E4D88A11A28AF7A001660AF 771 | 772 | buildConfigurations 773 | 774 | 9E4D88A21A28AF7A001660AF 775 | 9E4D88A31A28AF7A001660AF 776 | 777 | defaultConfigurationIsVisible 778 | 0 779 | isa 780 | XCConfigurationList 781 | 782 | 9E4D88A21A28AF7A001660AF 783 | 784 | baseConfigurationReference 785 | 725A67A7D2C70DAA000F7CFA 786 | buildSettings 787 | 788 | ASSETCATALOG_COMPILER_APPICON_NAME 789 | AppIcon 790 | INFOPLIST_FILE 791 | MCGraphViewDemo/Info.plist 792 | LD_RUNPATH_SEARCH_PATHS 793 | $(inherited) @executable_path/Frameworks 794 | PRODUCT_NAME 795 | $(TARGET_NAME) 796 | 797 | isa 798 | XCBuildConfiguration 799 | name 800 | Debug 801 | 802 | 9E4D88A31A28AF7A001660AF 803 | 804 | baseConfigurationReference 805 | 71FDC134D96DC7F2947D9158 806 | buildSettings 807 | 808 | ASSETCATALOG_COMPILER_APPICON_NAME 809 | AppIcon 810 | INFOPLIST_FILE 811 | MCGraphViewDemo/Info.plist 812 | LD_RUNPATH_SEARCH_PATHS 813 | $(inherited) @executable_path/Frameworks 814 | PRODUCT_NAME 815 | $(TARGET_NAME) 816 | 817 | isa 818 | XCBuildConfiguration 819 | name 820 | Release 821 | 822 | 9E4D88A41A28AF7A001660AF 823 | 824 | buildConfigurations 825 | 826 | 9E4D88A51A28AF7A001660AF 827 | 9E4D88A61A28AF7A001660AF 828 | 829 | defaultConfigurationIsVisible 830 | 0 831 | isa 832 | XCConfigurationList 833 | 834 | 9E4D88A51A28AF7A001660AF 835 | 836 | buildSettings 837 | 838 | BUNDLE_LOADER 839 | $(TEST_HOST) 840 | FRAMEWORK_SEARCH_PATHS 841 | 842 | $(SDKROOT)/Developer/Library/Frameworks 843 | $(inherited) 844 | 845 | GCC_PREPROCESSOR_DEFINITIONS 846 | 847 | DEBUG=1 848 | $(inherited) 849 | 850 | INFOPLIST_FILE 851 | MCGraphViewDemoTests/Info.plist 852 | LD_RUNPATH_SEARCH_PATHS 853 | $(inherited) @executable_path/Frameworks @loader_path/Frameworks 854 | PRODUCT_NAME 855 | $(TARGET_NAME) 856 | TEST_HOST 857 | $(BUILT_PRODUCTS_DIR)/MCGraphViewDemo.app/MCGraphViewDemo 858 | 859 | isa 860 | XCBuildConfiguration 861 | name 862 | Debug 863 | 864 | 9E4D88A61A28AF7A001660AF 865 | 866 | buildSettings 867 | 868 | BUNDLE_LOADER 869 | $(TEST_HOST) 870 | FRAMEWORK_SEARCH_PATHS 871 | 872 | $(SDKROOT)/Developer/Library/Frameworks 873 | $(inherited) 874 | 875 | INFOPLIST_FILE 876 | MCGraphViewDemoTests/Info.plist 877 | LD_RUNPATH_SEARCH_PATHS 878 | $(inherited) @executable_path/Frameworks @loader_path/Frameworks 879 | PRODUCT_NAME 880 | $(TARGET_NAME) 881 | TEST_HOST 882 | $(BUILT_PRODUCTS_DIR)/MCGraphViewDemo.app/MCGraphViewDemo 883 | 884 | isa 885 | XCBuildConfiguration 886 | name 887 | Release 888 | 889 | A6F93E6A383CB558D05964BF 890 | 891 | children 892 | 893 | 725A67A7D2C70DAA000F7CFA 894 | 71FDC134D96DC7F2947D9158 895 | 896 | isa 897 | PBXGroup 898 | name 899 | Pods 900 | sourceTree 901 | <group> 902 | 903 | C460D89EB4636E5E0F802047 904 | 905 | buildActionMask 906 | 2147483647 907 | files 908 | 909 | inputPaths 910 | 911 | isa 912 | PBXShellScriptBuildPhase 913 | name 914 | Copy Pods Resources 915 | outputPaths 916 | 917 | runOnlyForDeploymentPostprocessing 918 | 0 919 | shellPath 920 | /bin/sh 921 | shellScript 922 | "${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh" 923 | 924 | showEnvVarsInLog 925 | 0 926 | 927 | EF68330B2411175BA79D2D0C 928 | 929 | fileRef 930 | 7D51C8A3E5851707DAE92AEE 931 | isa 932 | PBXBuildFile 933 | 934 | FCDE11C8F051296BA6C46F24 935 | 936 | children 937 | 938 | 7D51C8A3E5851707DAE92AEE 939 | 940 | isa 941 | PBXGroup 942 | name 943 | Frameworks 944 | sourceTree 945 | <group> 946 | 947 | 948 | rootObject 949 | 9E4D88761A28AF7A001660AF 950 | 951 | 952 | -------------------------------------------------------------------------------- /MCGraphViewDemo/MCGraphViewDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // MCGraphViewDemo 4 | // 5 | // Created by Matthew Cheok on 28/11/14. 6 | // Copyright (c) 2014 Matthew Cheok. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /MCGraphViewDemo/MCGraphViewDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // MCGraphViewDemo 4 | // 5 | // Created by Matthew Cheok on 28/11/14. 6 | // Copyright (c) 2014 Matthew Cheok. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /MCGraphViewDemo/MCGraphViewDemo/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /MCGraphViewDemo/MCGraphViewDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /MCGraphViewDemo/MCGraphViewDemo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /MCGraphViewDemo/MCGraphViewDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.matthewcheok.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /MCGraphViewDemo/MCGraphViewDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // MCGraphViewDemo 4 | // 5 | // Created by Matthew Cheok on 28/11/14. 6 | // Copyright (c) 2014 Matthew Cheok. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /MCGraphViewDemo/MCGraphViewDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // MCGraphViewDemo 4 | // 5 | // Created by Matthew Cheok on 28/11/14. 6 | // Copyright (c) 2014 Matthew Cheok. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import 11 | 12 | @interface ViewController () 13 | 14 | @property (strong, nonatomic) IBOutlet MCGraphView *graphView; 15 | 16 | @end 17 | 18 | @implementation ViewController 19 | 20 | - (IBAction)randomizeLines:(id)sender { 21 | NSInteger numberOfLines = arc4random() % 3 + 2; 22 | 23 | NSMutableArray *lines = [NSMutableArray array]; 24 | for (int i=0; i 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /MCGraphViewDemo/MCGraphViewDemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.matthewcheok.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /MCGraphViewDemo/MCGraphViewDemoTests/MCGraphViewDemoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // MCGraphViewDemoTests.m 3 | // MCGraphViewDemoTests 4 | // 5 | // Created by Matthew Cheok on 28/11/14. 6 | // Copyright (c) 2014 Matthew Cheok. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface MCGraphViewDemoTests : XCTestCase 13 | 14 | @end 15 | 16 | @implementation MCGraphViewDemoTests 17 | 18 | - (void)setUp { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | [super tearDown]; 26 | } 27 | 28 | - (void)testExample { 29 | // This is an example of a functional test case. 30 | XCTAssert(YES, @"Pass"); 31 | } 32 | 33 | - (void)testPerformanceExample { 34 | // This is an example of a performance test case. 35 | [self measureBlock:^{ 36 | // Put the code you want to measure the time of here. 37 | }]; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /MCGraphViewDemo/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, "7.0" 3 | 4 | pod 'MCGraphView', :path => '../' 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MCGraphView ![License MIT](https://go-shields.herokuapp.com/license-MIT-blue.png) 2 | ======================== 3 | 4 | [![Badge w/ Version](https://cocoapod-badges.herokuapp.com/v/MCGraphView/badge.png)](https://github.com/matthewcheok/MCGraphView) 5 | [![Badge w/ Platform](https://cocoapod-badges.herokuapp.com/p/MCGraphView/badge.svg)](https://github.com/matthewcheok/MCGraphView) 6 | 7 | A light-weight solution for displaying graphs. 8 | 9 | ##Screenshot 10 | ![Screenshot](https://raw.github.com/matthewcheok/MCGraphView/master/MCGraphViewDemo.gif "Example of MCGraphView") 11 | 12 | ## Installation 13 | 14 | Add the following to your [CocoaPods](http://cocoapods.org/) Podfile 15 | 16 | pod 'MCGraphView', '~> 0.1' 17 | 18 | or clone as a git submodule, 19 | 20 | or just copy files in the ```MCGraphView``` folder into your project. 21 | 22 | ## Using MCGraphView 23 | 24 | Add the `MCGraphView` class either programmatically or assign the custom class via storyboard. Then set the `lineData` property: 25 | 26 | ``` 27 | self.graphView.lineData = @[ 28 | @[ 29 | [NSValue valueWithCGPoint:CGPointMake(1, 20)], 30 | [NSValue valueWithCGPoint:CGPointMake(2, 40)], 31 | [NSValue valueWithCGPoint:CGPointMake(3, 20)], 32 | [NSValue valueWithCGPoint:CGPointMake(4, 60)], 33 | [NSValue valueWithCGPoint:CGPointMake(5, 40)], 34 | [NSValue valueWithCGPoint:CGPointMake(6, 140)], 35 | [NSValue valueWithCGPoint:CGPointMake(7, 80)], 36 | ], 37 | @[ 38 | [NSValue valueWithCGPoint:CGPointMake(1, 40)], 39 | [NSValue valueWithCGPoint:CGPointMake(2, 20)], 40 | [NSValue valueWithCGPoint:CGPointMake(3, 60)], 41 | [NSValue valueWithCGPoint:CGPointMake(4, 100)], 42 | [NSValue valueWithCGPoint:CGPointMake(5, 60)], 43 | [NSValue valueWithCGPoint:CGPointMake(6, 20)], 44 | [NSValue valueWithCGPoint:CGPointMake(7, 60)], 45 | ] 46 | ]; 47 | ``` 48 | 49 | Then tell the view to reload data: 50 | 51 | ``` 52 | [self.graphView reloadDataAnimated:YES]; 53 | ``` 54 | 55 | The view automatically determines the ideal region to display based on the maximum and minimum values of your points. 56 | 57 | More configuration options are available via the delegate protocol `MCGraphViewDelegate`. See the demo project for details. 58 | 59 | ## Credits 60 | 61 | Inspired by [GraphKit](https://github.com/michalkonturek/GraphKit) and [BEMSimpleLineGraph](https://github.com/Boris-Em/BEMSimpleLineGraph). 62 | 63 | ## License 64 | 65 | MCGraphView is under the MIT license. 66 | --------------------------------------------------------------------------------