// // BEMLine.m // SimpleLineGraph // // Created by Bobo on 12/27/13. Updated by Sam Spencer on 1/11/14. // Copyright (c) 2013 Boris Emorine. All rights reserved. // Copyright (c) 2014 Sam Spencer. // #import "BEMLine.h" @implementation BEMLine - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { // Initialization code self.backgroundColor = [UIColor clearColor]; _frameOffset = 0.0; } return self; } - (void)drawRect:(CGRect)rect { // ---------------------------// // --- Draw Refrence Lines ---// // ---------------------------// UIBezierPath *referenceLinesPath = [UIBezierPath bezierPath]; referenceLinesPath.lineCapStyle = kCGLineCapButt; referenceLinesPath.lineWidth = 0.7; if (self.enableRefrenceLines == YES) { for (NSNumber *xNumber in self.arrayOfVerticalRefrenceLinePoints) { CGPoint initialPoint = CGPointMake([xNumber floatValue], self.frame.size.height - self.frameOffset); CGPoint finalPoint = CGPointMake([xNumber floatValue], 0); [referenceLinesPath moveToPoint:initialPoint]; [referenceLinesPath addLineToPoint:finalPoint]; } if (self.arrayOfHorizontalRefrenceLinePoints.count > 0) { for (NSNumber *yNumber in self.arrayOfHorizontalRefrenceLinePoints) { CGPoint initialPoint = CGPointMake(0, [yNumber floatValue]); CGPoint finalPoint = CGPointMake(self.frame.size.width, [yNumber floatValue]); [referenceLinesPath moveToPoint:initialPoint]; [referenceLinesPath addLineToPoint:finalPoint]; } } if (self.enableRefrenceFrame == YES) { [referenceLinesPath moveToPoint:CGPointMake(0, self.frame.size.height - self.frameOffset)]; [referenceLinesPath addLineToPoint:CGPointMake(self.frame.size.width, self.frame.size.height - self.frameOffset)]; [referenceLinesPath moveToPoint:CGPointMake(0+self.lineWidth/4, self.frame.size.height - self.frameOffset)]; [referenceLinesPath addLineToPoint:CGPointMake(0+self.lineWidth/4, 0)]; } [referenceLinesPath closePath]; } else referenceLinesPath = nil; // ---------------------------// // ----- Draw Graph Line -----// // ---------------------------// CGPoint CP1; CGPoint CP2; // BEZIER CURVE if (self.bezierCurveIsEnabled == YES) { // First control point CP1 = CGPointMake(self.P1.x + (self.P2.x - self.P1.x)/3, self.P1.y - (self.P1.y - self.P2.y)/3 - (self.P0.y - self.P1.y)*0.3); // Second control point CP2 = CGPointMake(self.P1.x + 2*(self.P2.x - self.P1.x)/3, (self.P1.y - 2*(self.P1.y - self.P2.y)/3) + (self.P2.y - self.P3.y)*0.3); } // LINE UIBezierPath *line = [UIBezierPath bezierPath]; UIBezierPath *fillTop = [UIBezierPath bezierPath]; UIBezierPath *fillBottom = [UIBezierPath bezierPath]; CGPoint p0; CGPoint p1; CGPoint p2; CGPoint p3; CGFloat tensionBezier1 = 0.3; CGFloat tensionBezier2 = 0.3; if (self.xAxisBackgroundColor == self.bottomColor && self.xAxisBackgroundAlpha == self.bottomAlpha) { [fillBottom moveToPoint:CGPointMake(self.frame.size.width, self.frame.size.height)]; [fillBottom addLineToPoint:CGPointMake(0, self.frame.size.height)]; } else { UIBezierPath *fillxAxis = [UIBezierPath bezierPath]; [fillBottom moveToPoint:CGPointMake(self.frame.size.width, self.frame.size.height - self.frameOffset)]; [fillBottom addLineToPoint:CGPointMake(0, self.frame.size.height - self.frameOffset)]; [fillxAxis moveToPoint:CGPointMake(self.frame.size.width, self.frame.size.height - self.frameOffset)]; [fillxAxis addLineToPoint:CGPointMake(0, self.frame.size.height - self.frameOffset)]; [fillxAxis addLineToPoint:CGPointMake(0, self.frame.size.height)]; [fillxAxis addLineToPoint:CGPointMake(self.frame.size.width, self.frame.size.height)]; [self.xAxisBackgroundColor set]; [fillxAxis fillWithBlendMode:kCGBlendModeNormal alpha:self.xAxisBackgroundAlpha]; } [fillTop moveToPoint:CGPointMake(self.frame.size.width, 0)]; [fillTop addLineToPoint:CGPointMake(0, 0)]; for (int i = 0; i<[self.arrayOfPoints count]-1; i++) { p1 = CGPointMake((self.frame.size.width/([self.arrayOfPoints count] - 1))*i, [[self.arrayOfPoints objectAtIndex:i] floatValue]); p2 = CGPointMake((self.frame.size.width/([self.arrayOfPoints count] - 1))*(i+1), [[self.arrayOfPoints objectAtIndex:i+1] floatValue]); [line moveToPoint:p1]; [fillBottom addLineToPoint:p1]; [fillTop addLineToPoint:p1]; if (self.bezierCurveIsEnabled == YES) { tensionBezier1 = 0.3; tensionBezier2 = 0.3; if (i > 0) { // Exception for first line because there is no previous point p0 = CGPointMake((self.frame.size.width/([self.arrayOfPoints count] - 1))*(i-1), [[self.arrayOfPoints objectAtIndex:i-1] floatValue]); if ([[self.arrayOfValues objectAtIndexedSubscript:i+1] floatValue] - [[self.arrayOfValues objectAtIndexedSubscript:i] floatValue] == [[self.arrayOfValues objectAtIndexedSubscript:i] floatValue] - [[self.arrayOfValues objectAtIndexedSubscript:i-1] floatValue]) { tensionBezier1 = 0; } } else { tensionBezier1 = 0; p0 = p1; } if (i<[self.arrayOfPoints count] - 2) { // Exception for last line because there is no next point p3 = CGPointMake((self.frame.size.width/([self.arrayOfPoints count] - 1))*(i+2), [[self.arrayOfPoints objectAtIndex:i+2] floatValue]); if ([[self.arrayOfValues objectAtIndexedSubscript:i+2] floatValue] - [[self.arrayOfValues objectAtIndexedSubscript:i+1] floatValue] == [[self.arrayOfValues objectAtIndexedSubscript:i+1] floatValue] - [[self.arrayOfValues objectAtIndexedSubscript:i] floatValue]) { tensionBezier2 = 0; } } else { p3 = p2; tensionBezier2 = 0; } // The tension should never exceed 0.3 if (tensionBezier1 > 0.3) { tensionBezier1 = 0.3; } if (tensionBezier2 > 0.3) { tensionBezier2 = 0.3; } // First control point CP1 = CGPointMake(p1.x + (p2.x - p1.x)/3, p1.y - (p1.y - p2.y)/3 - (p0.y - p1.y)*tensionBezier1); // Second control point CP2 = CGPointMake(p1.x + 2*(p2.x - p1.x)/3, (p1.y - 2*(p1.y - p2.y)/3) + (p2.y - p3.y)*tensionBezier2); [line addCurveToPoint:p2 controlPoint1:CP1 controlPoint2:CP2]; [fillBottom addCurveToPoint:p2 controlPoint1:CP1 controlPoint2:CP2]; [fillTop addCurveToPoint:p2 controlPoint1:CP1 controlPoint2:CP2]; } else { [line addLineToPoint:p2]; [fillBottom addLineToPoint:p2]; [fillTop addLineToPoint:p2]; } } // ---------------------------// // ---- Draw Fill Colors -----// // ---------------------------// [self.topColor set]; [fillTop fillWithBlendMode:kCGBlendModeNormal alpha:self.topAlpha]; [self.bottomColor set]; [fillBottom fillWithBlendMode:kCGBlendModeNormal alpha:self.bottomAlpha]; // ---------------------------// // ----- Animate Drawing -----// // ---------------------------// if (self.animationTime == 0) { if (self.enableRefrenceLines == YES) { [referenceLinesPath setLineWidth:self.lineWidth/2]; if (self.refrenceLineColor) { [self.refrenceLineColor set]; } else { [self.color set]; } [referenceLinesPath strokeWithBlendMode:kCGBlendModeNormal alpha:self.lineAlpha/2]; } [self.color set]; [line setLineWidth:self.lineWidth]; [line strokeWithBlendMode:kCGBlendModeNormal alpha:self.lineAlpha]; } else { if (self.enableRefrenceLines == YES) { CAShapeLayer *referenceLinesPathLayer = [CAShapeLayer layer]; referenceLinesPathLayer.frame = self.bounds; referenceLinesPathLayer.path = referenceLinesPath.CGPath; referenceLinesPathLayer.opacity = self.lineAlpha/2; referenceLinesPathLayer.fillColor = nil; referenceLinesPathLayer.lineWidth = self.lineWidth/2; if (self.refrenceLineColor) { referenceLinesPathLayer.strokeColor = self.refrenceLineColor.CGColor; } else { referenceLinesPathLayer.strokeColor = self.color.CGColor; } [self animateForLayer:referenceLinesPathLayer withAnimationType:self.animationType isAnimatingReferenceLine:YES]; [self.layer addSublayer:referenceLinesPathLayer]; } CAShapeLayer *pathLayer = [CAShapeLayer layer]; pathLayer.frame = self.bounds; pathLayer.path = line.CGPath; pathLayer.strokeColor = self.color.CGColor; pathLayer.fillColor = nil; pathLayer.lineWidth = self.lineWidth; pathLayer.lineJoin = kCALineJoinBevel; pathLayer.lineCap = kCALineCapRound; [self animateForLayer:pathLayer withAnimationType:self.animationType isAnimatingReferenceLine:NO]; [self.layer addSublayer:pathLayer]; } } - (void)animateForLayer:(CAShapeLayer *)shapeLayer withAnimationType:(BEMLineAnimation)animationType isAnimatingReferenceLine:(BOOL)shouldHalfOpacity { if (animationType == BEMLineAnimationNone) return; else if (animationType == BEMLineAnimationFade) { CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; pathAnimation.duration = self.animationTime; pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f]; if (shouldHalfOpacity == YES) pathAnimation.toValue = [NSNumber numberWithFloat:self.lineAlpha/2]; else pathAnimation.toValue = [NSNumber numberWithFloat:self.lineAlpha]; [shapeLayer addAnimation:pathAnimation forKey:@"opacity"]; return; } else { CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; pathAnimation.duration = self.animationTime; pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f]; pathAnimation.toValue = [NSNumber numberWithFloat:1.0f]; [shapeLayer addAnimation:pathAnimation forKey:@"strokeEnd"]; return; } } @end