More refactors
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// JBLineChartLinesView.h
|
||||
// JBChartViewDemo
|
||||
//
|
||||
// Created by Terry Worona on 12/26/15.
|
||||
// Copyright © 2015 Jawbone. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface JBLineChartLinesView : NSObject
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// JBLineChartLinesView.m
|
||||
// JBChartViewDemo
|
||||
//
|
||||
// Created by Terry Worona on 12/26/15.
|
||||
// Copyright © 2015 Jawbone. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JBLineChartLinesView.h"
|
||||
|
||||
@implementation JBLineChartLinesView
|
||||
|
||||
@end
|
||||
@@ -19,6 +19,7 @@
|
||||
5683C56D1C2E4EF90017B6BA /* JBGradientLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 5683C56C1C2E4EF90017B6BA /* JBGradientLayer.m */; };
|
||||
5683C5741C2E512C0017B6BA /* JBLineChartDotsView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5683C5731C2E512C0017B6BA /* JBLineChartDotsView.m */; };
|
||||
5683C57A1C2E54100017B6BA /* JBLineChartDotView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5683C5791C2E54100017B6BA /* JBLineChartDotView.m */; };
|
||||
5683C5801C2E597D0017B6BA /* JBLineChartLinesView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5683C57F1C2E597D0017B6BA /* JBLineChartLinesView.m */; };
|
||||
94BDFC3419F933B2007492F6 /* JBLineChartMissingPointsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 94BDFC3319F933B2007492F6 /* JBLineChartMissingPointsViewController.m */; };
|
||||
9B0725211829822A0052109B /* JBChartListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B0725201829822A0052109B /* JBChartListViewController.m */; };
|
||||
9B2E530518218CF20079B9D2 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B2E530418218CF20079B9D2 /* Foundation.framework */; };
|
||||
@@ -78,6 +79,8 @@
|
||||
5683C5731C2E512C0017B6BA /* JBLineChartDotsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JBLineChartDotsView.m; sourceTree = "<group>"; };
|
||||
5683C5781C2E54100017B6BA /* JBLineChartDotView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JBLineChartDotView.h; sourceTree = "<group>"; };
|
||||
5683C5791C2E54100017B6BA /* JBLineChartDotView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JBLineChartDotView.m; sourceTree = "<group>"; };
|
||||
5683C57E1C2E597D0017B6BA /* JBLineChartLinesView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JBLineChartLinesView.h; sourceTree = "<group>"; };
|
||||
5683C57F1C2E597D0017B6BA /* JBLineChartLinesView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JBLineChartLinesView.m; sourceTree = "<group>"; };
|
||||
94BDFC3219F933B2007492F6 /* JBLineChartMissingPointsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JBLineChartMissingPointsViewController.h; sourceTree = "<group>"; };
|
||||
94BDFC3319F933B2007492F6 /* JBLineChartMissingPointsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JBLineChartMissingPointsViewController.m; sourceTree = "<group>"; };
|
||||
9B07251F1829822A0052109B /* JBChartListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JBChartListViewController.h; sourceTree = "<group>"; };
|
||||
@@ -231,6 +234,8 @@
|
||||
5683C5731C2E512C0017B6BA /* JBLineChartDotsView.m */,
|
||||
5683C5781C2E54100017B6BA /* JBLineChartDotView.h */,
|
||||
5683C5791C2E54100017B6BA /* JBLineChartDotView.m */,
|
||||
5683C57E1C2E597D0017B6BA /* JBLineChartLinesView.h */,
|
||||
5683C57F1C2E597D0017B6BA /* JBLineChartLinesView.m */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
@@ -525,6 +530,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9BE0B0CE182B162E00232023 /* JBBarChartFooterView.m in Sources */,
|
||||
5683C5801C2E597D0017B6BA /* JBLineChartLinesView.m in Sources */,
|
||||
5683C5611C2E4D720017B6BA /* JBBarChartView.m in Sources */,
|
||||
9BEBE9D2183167050046E4A8 /* JBChartInformationView.m in Sources */,
|
||||
5683C5651C2E4D720017B6BA /* JBShapeLayer.m in Sources */,
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
// Views
|
||||
#import "JBLineChartDotsView.h"
|
||||
#import "JBLineChartLinesView.h"
|
||||
|
||||
// Enums
|
||||
typedef NS_ENUM(NSUInteger, JBLineChartHorizontalIndexClamp){
|
||||
@@ -29,18 +30,6 @@ typedef NS_ENUM(NSUInteger, JBLineChartHorizontalIndexClamp){
|
||||
JBLineChartHorizontalIndexClampNone
|
||||
};
|
||||
|
||||
// Numerics (JBLineChartLineView)
|
||||
CGFloat const kJBLineChartLinesViewStrokeWidth = 5.0;
|
||||
CGFloat const kJBLineChartLinesViewMiterLimit = -5.0;
|
||||
CGFloat const kJBLineChartLinesViewDefaultLinePhase = 1.0f;
|
||||
CGFloat const kJBLineChartLinesViewDefaultDimmedOpacity = 0.20f;
|
||||
CGFloat const kJBLineChartLinesViewSmoothThresholdSlope = 0.01f;
|
||||
CGFloat const kJBLineChartLinesViewReloadDataAnimationDuration = 0.15f;
|
||||
NSInteger const kJBLineChartLinesViewDefaultDotRadiusFactor = 3; // 3x size of line width
|
||||
NSInteger const kJBLineChartLinesViewSmoothThresholdVertical = 1;
|
||||
NSInteger const kJBLineChartLinesViewUnselectedLineIndex = -1;
|
||||
static NSArray *kJBLineChartLinesViewDefaultDashPattern = nil;
|
||||
|
||||
// Numerics (JBLineSelectionView)
|
||||
CGFloat const kJBLineSelectionViewWidth = 20.0f;
|
||||
|
||||
@@ -52,6 +41,9 @@ CGFloat const kJBLineChartViewStateBounceOffset = 15.0f;
|
||||
CGFloat const kJBLineChartViewDefaultStartPoint = 0.0;
|
||||
CGFloat const kJBLineChartViewDefaultEndPoint = 1.0;
|
||||
CGFloat const kJBLineChartViewReloadAnimationDuration = 0.1;
|
||||
CGFloat const kJBLineChartViewDefaultDimmedSelectionOpacity = 0.20f;
|
||||
CGFloat const kJBLineChartViewDefaultStrokeWidth = 5.0f;
|
||||
NSInteger const kJBLineChartViewDefaultDotRadiusFactor = 3; // 3x size of line width
|
||||
NSInteger const kJBLineChartUnselectedLineIndex = -1;
|
||||
|
||||
// Colors (JBLineChartView)
|
||||
@@ -70,48 +62,6 @@ static UIColor *kJBLineChartViewDefaultFillGradientEndColor = nil;
|
||||
|
||||
@end
|
||||
|
||||
@protocol JBLineChartLinesViewDelegate;
|
||||
|
||||
@interface JBLineChartLinesView : UIView
|
||||
|
||||
@property (nonatomic, assign) id<JBLineChartLinesViewDelegate> delegate;
|
||||
@property (nonatomic, assign) NSInteger selectedLineIndex; // -1 to unselect
|
||||
@property (nonatomic, assign) BOOL animated; // for reload
|
||||
|
||||
// Data
|
||||
- (void)reloadDataAnimated:(BOOL)animated callback:(void (^)())callback;
|
||||
- (void)reloadDataAnimated:(BOOL)animated;
|
||||
- (void)reloadData;
|
||||
|
||||
// Setters
|
||||
- (void)setSelectedLineIndex:(NSInteger)selectedLineIndex animated:(BOOL)animated;
|
||||
|
||||
// Getters
|
||||
- (UIBezierPath *)bezierPathForLineChartLine:(JBLineChartLine *)lineChartLine filled:(BOOL)filled;
|
||||
- (JBShapeLayer *)shapeLayerForLineIndex:(NSUInteger)lineIndex filled:(BOOL)filled;
|
||||
- (JBGradientLayer *)gradientLayerForLineIndex:(NSUInteger)lineIndex filled:(BOOL)filled;
|
||||
|
||||
// Callback helpers
|
||||
- (void)fireCallback:(void (^)())callback;
|
||||
|
||||
@end
|
||||
|
||||
@protocol JBLineChartLinesViewDelegate <NSObject>
|
||||
|
||||
- (NSArray *)lineChartLinesForLineChartLinesView:(JBLineChartLinesView *)lineChartLinesView;
|
||||
- (CGFloat)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView dimmedSelectionOpacityAtLineIndex:(NSUInteger)lineIndex;
|
||||
- (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView colorForLineAtLineIndex:(NSUInteger)lineIndex;
|
||||
- (CAGradientLayer *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView gradientForLineAtLineIndex:(NSUInteger)lineIndex;
|
||||
- (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView fillColorForLineAtLineIndex:(NSUInteger)lineIndex;
|
||||
- (CAGradientLayer *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView fillGradientForLineAtLineIndex:(NSUInteger)lineIndex;
|
||||
- (CGFloat)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView widthForLineAtLineIndex:(NSUInteger)lineIndex;
|
||||
- (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView selectionColorForLineAtLineIndex:(NSUInteger)lineIndex;
|
||||
- (CAGradientLayer *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView selectionGradientForLineAtLineIndex:(NSUInteger)lineIndex;
|
||||
- (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView selectionFillColorForLineAtLineIndex:(NSUInteger)lineIndex;
|
||||
- (CAGradientLayer *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView selectionFillGradientForLineAtLineIndex:(NSUInteger)lineIndex;
|
||||
|
||||
@end
|
||||
|
||||
@interface JBLineChartView () <JBLineChartLinesViewDelegate, JBLineChartDotsViewDelegate>
|
||||
|
||||
@property (nonatomic, strong) NSArray *lineChartLines; // Collection of JBLineChartLines
|
||||
@@ -527,7 +477,7 @@ static UIColor *kJBLineChartViewDefaultFillGradientEndColor = nil;
|
||||
showsDots = [self.dataSource lineChartView:self showsDotsForLineAtLineIndex:lineIndex];
|
||||
}
|
||||
|
||||
CGFloat lineWidth = kJBLineChartLinesViewStrokeWidth; // default
|
||||
CGFloat lineWidth = kJBLineChartViewDefaultStrokeWidth; // default
|
||||
if ([self.delegate respondsToSelector:@selector(lineChartView:widthForLineAtLineIndex:)])
|
||||
{
|
||||
lineWidth = [self.delegate lineChartView:self widthForLineAtLineIndex:lineIndex];
|
||||
@@ -594,7 +544,7 @@ static UIColor *kJBLineChartViewDefaultFillGradientEndColor = nil;
|
||||
}
|
||||
else
|
||||
{
|
||||
CGFloat defaultDotRadius = ((lineWidth * kJBLineChartLinesViewDefaultDotRadiusFactor) * 2.0f);
|
||||
CGFloat defaultDotRadius = ((lineWidth * kJBLineChartViewDefaultDotRadiusFactor) * 2.0f);
|
||||
if (defaultDotRadius > maxDotLength)
|
||||
{
|
||||
maxDotLength = defaultDotRadius;
|
||||
@@ -643,7 +593,7 @@ static UIColor *kJBLineChartViewDefaultFillGradientEndColor = nil;
|
||||
{
|
||||
return [self.dataSource lineChartView:self dimmedSelectionOpacityAtLineIndex:lineIndex];
|
||||
}
|
||||
return kJBLineChartLinesViewDefaultDimmedOpacity;
|
||||
return kJBLineChartViewDefaultDimmedSelectionOpacity;
|
||||
}
|
||||
|
||||
- (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView colorForLineAtLineIndex:(NSUInteger)lineIndex
|
||||
@@ -688,7 +638,7 @@ static UIColor *kJBLineChartViewDefaultFillGradientEndColor = nil;
|
||||
{
|
||||
return [self.delegate lineChartView:self widthForLineAtLineIndex:lineIndex];
|
||||
}
|
||||
return kJBLineChartLinesViewStrokeWidth;
|
||||
return kJBLineChartViewDefaultStrokeWidth;
|
||||
}
|
||||
|
||||
- (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView selectionColorForLineAtLineIndex:(NSUInteger)lineIndex
|
||||
@@ -758,7 +708,7 @@ static UIColor *kJBLineChartViewDefaultFillGradientEndColor = nil;
|
||||
{
|
||||
return [self.delegate lineChartView:self widthForLineAtLineIndex:lineIndex];
|
||||
}
|
||||
return kJBLineChartLinesViewStrokeWidth;
|
||||
return kJBLineChartViewDefaultStrokeWidth;
|
||||
}
|
||||
|
||||
- (CGFloat)lineChartDotsView:(JBLineChartDotsView *)lineChartDotsView dotRadiusForLineAtHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex
|
||||
@@ -767,7 +717,7 @@ static UIColor *kJBLineChartViewDefaultFillGradientEndColor = nil;
|
||||
{
|
||||
return [self.delegate lineChartView:self dotRadiusForDotAtHorizontalIndex:horizontalIndex atLineIndex:lineIndex];
|
||||
}
|
||||
return [self lineChartDotsView:lineChartDotsView widthForLineAtLineIndex:lineIndex] * kJBLineChartLinesViewDefaultDotRadiusFactor;
|
||||
return [self lineChartDotsView:lineChartDotsView widthForLineAtLineIndex:lineIndex] * kJBLineChartViewDefaultDotRadiusFactor;
|
||||
}
|
||||
|
||||
- (UIView *)lineChartDotsView:(JBLineChartDotsView *)lineChartDotsView dotViewAtHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex
|
||||
@@ -1208,589 +1158,3 @@ static UIColor *kJBLineChartViewDefaultFillGradientEndColor = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation JBLineChartLinesView
|
||||
|
||||
#pragma mark - Alloc/Init
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self == [JBLineChartLinesView class])
|
||||
{
|
||||
kJBLineChartLinesViewDefaultDashPattern = @[@(3), @(2)];
|
||||
}
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self)
|
||||
{
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Memory Management
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:self];
|
||||
}
|
||||
|
||||
#pragma mark - Drawing
|
||||
|
||||
- (void)drawRect:(CGRect)rect
|
||||
{
|
||||
[super drawRect:rect];
|
||||
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesForLineChartLinesView:)], @"JBLineChartLinesView // delegate must implement - (NSArray *)lineChartLinesForLineChartLinesView:(JBLineChartLinesView *)lineChartLinesView");
|
||||
NSArray *chartData = [self.delegate lineChartLinesForLineChartLinesView:self];
|
||||
|
||||
for (int lineIndex=0; lineIndex<[chartData count]; lineIndex++)
|
||||
{
|
||||
JBLineChartLine *lineChartLine = [chartData objectAtIndex:lineIndex];
|
||||
{
|
||||
UIBezierPath *linePath = [self bezierPathForLineChartLine:lineChartLine filled:NO];
|
||||
UIBezierPath *fillPath = [self bezierPathForLineChartLine:lineChartLine filled:YES];
|
||||
|
||||
if (linePath == nil || fillPath == nil)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
JBShapeLayer *shapeLayer = [self shapeLayerForLineIndex:lineIndex filled:NO];
|
||||
if (shapeLayer == nil)
|
||||
{
|
||||
shapeLayer = [[JBShapeLayer alloc] initWithTag:lineIndex filled:NO currentPath:linePath];
|
||||
}
|
||||
|
||||
JBShapeLayer *fillLayer = [self shapeLayerForLineIndex:lineIndex filled:YES];
|
||||
if (fillLayer == nil)
|
||||
{
|
||||
fillLayer = [[JBShapeLayer alloc] initWithTag:lineIndex filled:YES currentPath:nil]; // don't need currentPath since fill's aren't animatable (yet)
|
||||
}
|
||||
|
||||
shapeLayer.zPosition = 0.1f;
|
||||
shapeLayer.fillColor = [UIColor clearColor].CGColor;
|
||||
fillLayer.zPosition = 0.1f;
|
||||
fillLayer.fillColor = [UIColor clearColor].CGColor;
|
||||
|
||||
// Line style
|
||||
if (lineChartLine.lineStyle == JBLineChartViewLineStyleSolid)
|
||||
{
|
||||
shapeLayer.lineDashPhase = 0.0;
|
||||
shapeLayer.lineDashPattern = nil;
|
||||
}
|
||||
else if (lineChartLine.lineStyle == JBLineChartViewLineStyleDashed)
|
||||
{
|
||||
shapeLayer.lineDashPhase = kJBLineChartLinesViewDefaultLinePhase;
|
||||
shapeLayer.lineDashPattern = kJBLineChartLinesViewDefaultDashPattern;
|
||||
}
|
||||
|
||||
// Smoothing
|
||||
if (lineChartLine.smoothedLine)
|
||||
{
|
||||
if (lineChartLine.lineStyle == JBLineChartViewLineStyleDashed)
|
||||
{
|
||||
shapeLayer.lineCap = kCALineCapButt; // smoothed, dashed lines need butt caps
|
||||
}
|
||||
else
|
||||
{
|
||||
shapeLayer.lineCap = kCALineCapRound;
|
||||
}
|
||||
shapeLayer.lineJoin = kCALineJoinRound;
|
||||
fillLayer.lineCap = kCALineCapRound;
|
||||
fillLayer.lineJoin = kCALineJoinRound;
|
||||
}
|
||||
else
|
||||
{
|
||||
shapeLayer.lineCap = kCALineCapButt;
|
||||
shapeLayer.lineJoin = kCALineJoinMiter;
|
||||
fillLayer.lineCap = kCALineCapButt;
|
||||
fillLayer.lineJoin = kCALineJoinMiter;
|
||||
}
|
||||
|
||||
// Width
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:widthForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (CGFloat)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView widthForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
shapeLayer.lineWidth = [self.delegate lineChartLinesView:self widthForLineAtLineIndex:lineIndex];
|
||||
fillLayer.lineWidth = [self.delegate lineChartLinesView:self widthForLineAtLineIndex:lineIndex];
|
||||
|
||||
// Colors
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:colorForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView colorForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
shapeLayer.strokeColor = [self.delegate lineChartLinesView:self colorForLineAtLineIndex:lineIndex].CGColor;
|
||||
|
||||
// Line path
|
||||
shapeLayer.frame = self.bounds;
|
||||
if (self.animated)
|
||||
{
|
||||
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
|
||||
pathAnimation.fromValue = (id)shapeLayer.currentPath.CGPath;
|
||||
pathAnimation.toValue = (id)linePath.CGPath;
|
||||
pathAnimation.duration = kJBLineChartLinesViewReloadDataAnimationDuration;
|
||||
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:@"easeInEaseOut"];
|
||||
pathAnimation.fillMode = kCAFillModeBoth;
|
||||
pathAnimation.removedOnCompletion = NO;
|
||||
[shapeLayer addAnimation:pathAnimation forKey:@"shapeLayerPathAnimation"];
|
||||
}
|
||||
else
|
||||
{
|
||||
shapeLayer.path = linePath.CGPath;
|
||||
}
|
||||
shapeLayer.currentPath = [linePath copy];
|
||||
|
||||
// Fill path
|
||||
fillLayer.frame = self.bounds;
|
||||
fillLayer.path = fillPath.CGPath;
|
||||
|
||||
// Solid fill
|
||||
if (lineChartLine.fillColorStyle == JBLineChartViewColorStyleSolid)
|
||||
{
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:fillColorForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView fillColorForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
fillLayer.fillColor = [self.delegate lineChartLinesView:self fillColorForLineAtLineIndex:lineIndex].CGColor;
|
||||
[self.layer addSublayer:fillLayer];
|
||||
}
|
||||
|
||||
// Gradient fill
|
||||
else if (lineChartLine.fillColorStyle == JBLineChartViewColorStyleGradient)
|
||||
{
|
||||
JBGradientLayer *fillGradientLayer = [self gradientLayerForLineIndex:lineIndex filled:YES];
|
||||
if (fillGradientLayer == nil)
|
||||
{
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:fillGradientForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (CAGradientLayer *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView fillGradientForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
fillGradientLayer = [[JBGradientLayer alloc] initWithGradientLayer:[self.delegate lineChartLinesView:self fillGradientForLineAtLineIndex:lineIndex] tag:lineIndex filled:YES currentPath:nil];
|
||||
}
|
||||
fillGradientLayer.frame = fillLayer.frame;
|
||||
fillGradientLayer.mask = fillLayer;
|
||||
[self.layer addSublayer:fillGradientLayer];
|
||||
}
|
||||
|
||||
// Solid line
|
||||
if (lineChartLine.colorStyle == JBLineChartViewColorStyleSolid)
|
||||
{
|
||||
[self.layer addSublayer:shapeLayer];
|
||||
}
|
||||
|
||||
// Gradient line
|
||||
else if (lineChartLine.colorStyle == JBLineChartViewColorStyleGradient)
|
||||
{
|
||||
JBGradientLayer *gradientLayer = [self gradientLayerForLineIndex:lineIndex filled:NO];
|
||||
if (gradientLayer == nil)
|
||||
{
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:gradientForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (CAGradientLayer *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView gradientForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
gradientLayer = [[JBGradientLayer alloc] initWithGradientLayer:[self.delegate lineChartLinesView:self gradientForLineAtLineIndex:lineIndex] tag:lineIndex filled:NO currentPath:linePath];
|
||||
}
|
||||
gradientLayer.frame = shapeLayer.frame;
|
||||
|
||||
if (self.animated)
|
||||
{
|
||||
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
|
||||
pathAnimation.fromValue = (id)gradientLayer.currentPath.CGPath;
|
||||
pathAnimation.toValue = (id)linePath.CGPath;
|
||||
pathAnimation.duration = kJBLineChartLinesViewReloadDataAnimationDuration;
|
||||
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:@"easeInEaseOut"];
|
||||
pathAnimation.fillMode = kCAFillModeBoth;
|
||||
pathAnimation.removedOnCompletion = NO;
|
||||
[gradientLayer.mask addAnimation:pathAnimation forKey:@"gradientLayerMaskAnimation"];
|
||||
}
|
||||
else
|
||||
{
|
||||
gradientLayer.mask = shapeLayer;
|
||||
}
|
||||
gradientLayer.currentPath = [linePath copy];
|
||||
|
||||
[self.layer addSublayer:gradientLayer];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.animated = NO;
|
||||
}
|
||||
|
||||
#pragma mark - Data
|
||||
|
||||
- (void)reloadDataAnimated:(BOOL)animated callback:(void (^)())callback
|
||||
{
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesForLineChartLinesView:)], @"JBLineChartLinesView // delegate must implement - (NSArray *)lineChartLinesForLineChartLinesView:(JBLineChartLinesView *)lineChartLinesView");
|
||||
NSArray *chartData = [self.delegate lineChartLinesForLineChartLinesView:self];
|
||||
|
||||
NSUInteger lineCount = [chartData count];
|
||||
|
||||
__weak JBLineChartLinesView* weakSelf = self;
|
||||
|
||||
dispatch_block_t completionBlock = ^{
|
||||
weakSelf.animated = NO;
|
||||
[weakSelf setNeedsDisplay]; // re-draw layers
|
||||
if (callback)
|
||||
{
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
// Mark layers for animation or removal
|
||||
NSMutableArray *mutableRemovedLayers = [NSMutableArray array];
|
||||
for (CALayer *layer in [self.layer sublayers])
|
||||
{
|
||||
BOOL removeLayer = NO;
|
||||
|
||||
if ([layer isKindOfClass:[JBShapeLayer class]])
|
||||
{
|
||||
removeLayer = (((JBShapeLayer *)layer).tag >= lineCount);
|
||||
}
|
||||
else if ([layer isKindOfClass:[JBGradientLayer class]])
|
||||
{
|
||||
removeLayer = (((JBGradientLayer *)layer).tag >= lineCount);
|
||||
}
|
||||
|
||||
if (removeLayer)
|
||||
{
|
||||
[mutableRemovedLayers addObject:layer];
|
||||
}
|
||||
}
|
||||
|
||||
// Remove legacy layers
|
||||
NSArray *removedLayers = [NSArray arrayWithArray:mutableRemovedLayers];
|
||||
if ([removedLayers count] > 0)
|
||||
{
|
||||
for (int index=0; index<[removedLayers count]; index++)
|
||||
{
|
||||
CALayer *removedLayer = [removedLayers objectAtIndex:index];
|
||||
|
||||
if (animated)
|
||||
{
|
||||
[CATransaction begin];
|
||||
{
|
||||
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
|
||||
animation.fromValue = [NSNumber numberWithFloat:1.0f];
|
||||
animation.toValue = [NSNumber numberWithFloat:0.0f];
|
||||
animation.duration = kJBLineChartLinesViewReloadDataAnimationDuration;
|
||||
animation.timingFunction = [CAMediaTimingFunction functionWithName:@"easeInEaseOut"];
|
||||
animation.fillMode = kCAFillModeBoth;
|
||||
animation.removedOnCompletion = NO;
|
||||
|
||||
[CATransaction setCompletionBlock:^{
|
||||
[removedLayer removeFromSuperlayer];
|
||||
if (index == [removedLayers count]-1)
|
||||
{
|
||||
completionBlock();
|
||||
}
|
||||
}];
|
||||
|
||||
[removedLayer addAnimation:animation forKey:@"removeShapeLayerAnimation"];
|
||||
}
|
||||
[CATransaction commit];
|
||||
}
|
||||
else
|
||||
{
|
||||
[removedLayer removeFromSuperlayer];
|
||||
if (index == [removedLayers count]-1)
|
||||
{
|
||||
completionBlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
completionBlock();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reloadDataAnimated:(BOOL)animated
|
||||
{
|
||||
[self reloadDataAnimated:animated callback:nil];
|
||||
}
|
||||
|
||||
- (void)reloadData
|
||||
{
|
||||
[self reloadDataAnimated:NO];
|
||||
}
|
||||
|
||||
#pragma mark - Setters
|
||||
|
||||
- (void)setSelectedLineIndex:(NSInteger)selectedLineIndex animated:(BOOL)animated
|
||||
{
|
||||
_selectedLineIndex = selectedLineIndex;
|
||||
|
||||
__weak JBLineChartLinesView* weakSelf = self;
|
||||
|
||||
dispatch_block_t adjustLines = ^{
|
||||
NSMutableArray *layersToReplace = [NSMutableArray array];
|
||||
|
||||
NSString * const oldLayerKey = @"oldLayer";
|
||||
NSString * const newLayerKey = @"newLayer";
|
||||
|
||||
for (CALayer *layer in [weakSelf.layer sublayers])
|
||||
{
|
||||
/*
|
||||
* Solid line or fill
|
||||
*/
|
||||
if ([layer isKindOfClass:[JBShapeLayer class]])
|
||||
{
|
||||
JBShapeLayer *shapeLayer = (JBShapeLayer * )layer;
|
||||
|
||||
if (shapeLayer.filled)
|
||||
{
|
||||
// Selected solid fill
|
||||
if (shapeLayer.tag == weakSelf.selectedLineIndex)
|
||||
{
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:selectionFillColorForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView selectionFillColorForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
shapeLayer.fillColor = [self.delegate lineChartLinesView:self selectionFillColorForLineAtLineIndex:shapeLayer.tag].CGColor;
|
||||
shapeLayer.opacity = 1.0f;
|
||||
}
|
||||
// Unselected solid fill
|
||||
else
|
||||
{
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:fillColorForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView fillColorForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
shapeLayer.fillColor = [self.delegate lineChartLinesView:self fillColorForLineAtLineIndex:shapeLayer.tag].CGColor;
|
||||
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:dimmedSelectionOpacityAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (CGFloat)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView dimmedSelectionOpacityAtLineIndex:(NSUInteger)lineIndex");
|
||||
shapeLayer.opacity = (weakSelf.selectedLineIndex == kJBLineChartLinesViewUnselectedLineIndex) ? 1.0f : [self.delegate lineChartLinesView:self dimmedSelectionOpacityAtLineIndex:shapeLayer.tag];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Selected solid line
|
||||
if (shapeLayer.tag == weakSelf.selectedLineIndex)
|
||||
{
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:selectionColorForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView selectionColorForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
shapeLayer.strokeColor = [self.delegate lineChartLinesView:self selectionColorForLineAtLineIndex:shapeLayer.tag].CGColor;
|
||||
shapeLayer.opacity = 1.0f;
|
||||
}
|
||||
// Unselected solid line
|
||||
else
|
||||
{
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:colorForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView colorForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
shapeLayer.strokeColor = [self.delegate lineChartLinesView:self colorForLineAtLineIndex:shapeLayer.tag].CGColor;
|
||||
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:dimmedSelectionOpacityAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (CGFloat)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView dimmedSelectionOpacityAtLineIndex:(NSUInteger)lineIndex");
|
||||
shapeLayer.opacity = (weakSelf.selectedLineIndex == kJBLineChartLinesViewUnselectedLineIndex) ? 1.0f : [self.delegate lineChartLinesView:self dimmedSelectionOpacityAtLineIndex:shapeLayer.tag];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Gradient line or fill
|
||||
*/
|
||||
else if ([layer isKindOfClass:[CAGradientLayer class]])
|
||||
{
|
||||
CAGradientLayer *gradientLayer = (CAGradientLayer * )layer;
|
||||
|
||||
if ([gradientLayer.mask isKindOfClass:[JBShapeLayer class]])
|
||||
{
|
||||
JBShapeLayer *shapeLayer = (JBShapeLayer * )gradientLayer.mask;
|
||||
|
||||
if (shapeLayer.filled)
|
||||
{
|
||||
// Selected gradient fill
|
||||
if (shapeLayer.tag == weakSelf.selectedLineIndex)
|
||||
{
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:selectionFillGradientForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (CAGradientLayer *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView selectionFillGradientForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
CAGradientLayer *selectedFillGradient = [self.delegate lineChartLinesView:self selectionFillGradientForLineAtLineIndex:shapeLayer.tag];
|
||||
selectedFillGradient.frame = layer.frame;
|
||||
selectedFillGradient.mask = layer.mask;
|
||||
selectedFillGradient.opacity = 1.0f;
|
||||
[layersToReplace addObject:@{oldLayerKey: layer, newLayerKey: selectedFillGradient}];
|
||||
}
|
||||
// Unselected gradient fill
|
||||
else
|
||||
{
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:fillGradientForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (CAGradientLayer *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView fillGradientForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
CAGradientLayer *unselectedFillGradient = [self.delegate lineChartLinesView:self fillGradientForLineAtLineIndex:shapeLayer.tag];
|
||||
unselectedFillGradient.frame = layer.frame;
|
||||
unselectedFillGradient.mask = layer.mask;
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:dimmedSelectionOpacityAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (CGFloat)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView dimmedSelectionOpacityAtLineIndex:(NSUInteger)lineIndex");
|
||||
unselectedFillGradient.opacity = (weakSelf.selectedLineIndex == kJBLineChartLinesViewUnselectedLineIndex) ? 1.0f : [self.delegate lineChartLinesView:self dimmedSelectionOpacityAtLineIndex:shapeLayer.tag];
|
||||
[layersToReplace addObject:@{oldLayerKey: layer, newLayerKey: unselectedFillGradient}];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Selected gradient line
|
||||
if (shapeLayer.tag == weakSelf.selectedLineIndex)
|
||||
{
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:selectionGradientForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (CAGradientLayer *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView selectionGradientForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
CAGradientLayer *selectedGradient = [self.delegate lineChartLinesView:self selectionGradientForLineAtLineIndex:shapeLayer.tag];
|
||||
selectedGradient.frame = layer.frame;
|
||||
selectedGradient.mask = layer.mask;
|
||||
selectedGradient.opacity = 1.0f;
|
||||
[layersToReplace addObject:@{oldLayerKey: layer, newLayerKey: selectedGradient}];
|
||||
}
|
||||
// Unselected gradient line
|
||||
else
|
||||
{
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:gradientForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (CAGradientLayer *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView gradientForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
CAGradientLayer *unselectedGradient = [self.delegate lineChartLinesView:self gradientForLineAtLineIndex:shapeLayer.tag];
|
||||
unselectedGradient.frame = layer.frame;
|
||||
unselectedGradient.mask = layer.mask;
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:dimmedSelectionOpacityAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (CGFloat)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView dimmedSelectionOpacityAtLineIndex:(NSUInteger)lineIndex");
|
||||
shapeLayer.opacity = (weakSelf.selectedLineIndex == kJBLineChartLinesViewUnselectedLineIndex) ? 1.0f : [self.delegate lineChartLinesView:self dimmedSelectionOpacityAtLineIndex:shapeLayer.tag];
|
||||
[layersToReplace addObject:@{oldLayerKey: layer, newLayerKey: unselectedGradient}];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (NSDictionary *layerPair in layersToReplace)
|
||||
{
|
||||
[weakSelf.layer replaceSublayer:layerPair[oldLayerKey] with:layerPair[newLayerKey]];
|
||||
}
|
||||
};
|
||||
|
||||
if (animated)
|
||||
{
|
||||
[UIView animateWithDuration:kJBChartViewDefaultAnimationDuration animations:^{
|
||||
adjustLines();
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
adjustLines();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setSelectedLineIndex:(NSInteger)selectedLineIndex
|
||||
{
|
||||
[self setSelectedLineIndex:selectedLineIndex animated:NO];
|
||||
}
|
||||
|
||||
#pragma mark - Getters
|
||||
|
||||
- (UIBezierPath *)bezierPathForLineChartLine:(JBLineChartLine *)lineChartLine filled:(BOOL)filled
|
||||
{
|
||||
if ([lineChartLine.lineChartPoints count] > 0)
|
||||
{
|
||||
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
|
||||
|
||||
bezierPath.miterLimit = kJBLineChartLinesViewMiterLimit;
|
||||
|
||||
JBLineChartPoint *previousLineChartPoint = nil;
|
||||
CGFloat previousSlope = 0.0f;
|
||||
|
||||
BOOL visiblePointFound = NO;
|
||||
NSArray *sortedLineChartPoints = [lineChartLine.lineChartPoints sortedArrayUsingSelector:@selector(compare:)];
|
||||
CGFloat firstXPosition = 0.0f;
|
||||
CGFloat firstYPosition = 0.0f;
|
||||
CGFloat lastXPosition = 0.0f;
|
||||
CGFloat lastYPosition = 0.0f;
|
||||
|
||||
for (NSUInteger index=0; index<[sortedLineChartPoints count]; index++)
|
||||
{
|
||||
JBLineChartPoint *lineChartPoint = [sortedLineChartPoints objectAtIndex:index];
|
||||
|
||||
if (lineChartPoint.hidden)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!visiblePointFound)
|
||||
{
|
||||
[bezierPath moveToPoint:CGPointMake(lineChartPoint.position.x, lineChartPoint.position.y)];
|
||||
firstXPosition = lineChartPoint.position.x;
|
||||
firstYPosition = lineChartPoint.position.y;
|
||||
visiblePointFound = YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
JBLineChartPoint *nextLineChartPoint = nil;
|
||||
if (index != ([lineChartLine.lineChartPoints count] - 1))
|
||||
{
|
||||
nextLineChartPoint = [sortedLineChartPoints objectAtIndex:(index + 1)];
|
||||
}
|
||||
|
||||
CGFloat nextSlope = (nextLineChartPoint != nil) ? ((nextLineChartPoint.position.y - lineChartPoint.position.y)) / ((nextLineChartPoint.position.x - lineChartPoint.position.x)) : previousSlope;
|
||||
CGFloat currentSlope = ((lineChartPoint.position.y - previousLineChartPoint.position.y)) / (lineChartPoint.position.x-previousLineChartPoint.position.x);
|
||||
|
||||
BOOL deltaFromNextSlope = ((currentSlope >= (nextSlope + kJBLineChartLinesViewSmoothThresholdSlope)) || (currentSlope <= (nextSlope - kJBLineChartLinesViewSmoothThresholdSlope)));
|
||||
BOOL deltaFromPreviousSlope = ((currentSlope >= (previousSlope + kJBLineChartLinesViewSmoothThresholdSlope)) || (currentSlope <= (previousSlope - kJBLineChartLinesViewSmoothThresholdSlope)));
|
||||
BOOL deltaFromPreviousY = (lineChartPoint.position.y >= previousLineChartPoint.position.y + kJBLineChartLinesViewSmoothThresholdVertical) || (lineChartPoint.position.y <= previousLineChartPoint.position.y - kJBLineChartLinesViewSmoothThresholdVertical);
|
||||
|
||||
if (lineChartLine.smoothedLine && deltaFromNextSlope && deltaFromPreviousSlope && deltaFromPreviousY)
|
||||
{
|
||||
CGFloat deltaX = lineChartPoint.position.x - previousLineChartPoint.position.x;
|
||||
CGFloat controlPointX = previousLineChartPoint.position.x + (deltaX / 2);
|
||||
|
||||
CGPoint controlPoint1 = CGPointMake(controlPointX, previousLineChartPoint.position.y);
|
||||
CGPoint controlPoint2 = CGPointMake(controlPointX, lineChartPoint.position.y);
|
||||
|
||||
[bezierPath addCurveToPoint:CGPointMake(lineChartPoint.position.x, lineChartPoint.position.y) controlPoint1:controlPoint1 controlPoint2:controlPoint2];
|
||||
}
|
||||
else
|
||||
{
|
||||
[bezierPath addLineToPoint:CGPointMake(lineChartPoint.position.x, lineChartPoint.position.y)];
|
||||
}
|
||||
|
||||
lastXPosition = lineChartPoint.position.x;
|
||||
lastYPosition = lineChartPoint.position.y;
|
||||
previousSlope = currentSlope;
|
||||
}
|
||||
previousLineChartPoint = lineChartPoint;
|
||||
}
|
||||
|
||||
if (filled)
|
||||
{
|
||||
UIBezierPath *filledBezierPath = [bezierPath copy];
|
||||
|
||||
if(visiblePointFound)
|
||||
{
|
||||
[filledBezierPath addLineToPoint:CGPointMake(lastXPosition, lastYPosition)];
|
||||
[filledBezierPath addLineToPoint:CGPointMake(lastXPosition, self.bounds.size.height)];
|
||||
|
||||
[filledBezierPath addLineToPoint:CGPointMake(firstXPosition, self.bounds.size.height)];
|
||||
[filledBezierPath addLineToPoint:CGPointMake(firstXPosition, firstYPosition)];
|
||||
}
|
||||
|
||||
return filledBezierPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
return bezierPath;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (JBShapeLayer *)shapeLayerForLineIndex:(NSUInteger)lineIndex filled:(BOOL)filled
|
||||
{
|
||||
for (CALayer *layer in [self.layer sublayers])
|
||||
{
|
||||
if ([layer isKindOfClass:[JBShapeLayer class]])
|
||||
{
|
||||
if (((JBShapeLayer *)layer).tag == lineIndex && ((JBShapeLayer *)layer).filled == filled)
|
||||
{
|
||||
return (JBShapeLayer *)layer;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (JBGradientLayer *)gradientLayerForLineIndex:(NSUInteger)lineIndex filled:(BOOL)filled
|
||||
{
|
||||
for (CALayer *layer in [self.layer sublayers])
|
||||
{
|
||||
if ([layer isKindOfClass:[JBGradientLayer class]])
|
||||
{
|
||||
if (((JBGradientLayer *)layer).tag == lineIndex && ((JBGradientLayer *)layer).filled == filled)
|
||||
{
|
||||
return (JBGradientLayer *)layer;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark - Callback Helpers
|
||||
|
||||
- (void)fireCallback:(void (^)())callback
|
||||
{
|
||||
dispatch_block_t callbackCopy = [callback copy];
|
||||
|
||||
if (callbackCopy != nil)
|
||||
{
|
||||
callbackCopy();
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// JBLineChartLinesView.h
|
||||
// JBChartViewDemo
|
||||
//
|
||||
// Created by Terry Worona on 12/26/15.
|
||||
// Copyright © 2015 Jawbone. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
// Layers
|
||||
#import "JBShapeLayer.h"
|
||||
#import "JBGradientLayer.h"
|
||||
|
||||
// Models
|
||||
#import "JBLineChartLine.h"
|
||||
|
||||
// Numerics
|
||||
extern NSInteger const kJBLineChartLinesViewUnselectedLineIndex;
|
||||
|
||||
@protocol JBLineChartLinesViewDelegate;
|
||||
|
||||
@interface JBLineChartLinesView : UIView
|
||||
|
||||
@property (nonatomic, assign) id<JBLineChartLinesViewDelegate> delegate;
|
||||
@property (nonatomic, assign) NSInteger selectedLineIndex; // -1 to unselect
|
||||
@property (nonatomic, assign) BOOL animated; // for reload
|
||||
|
||||
// Data
|
||||
- (void)reloadDataAnimated:(BOOL)animated callback:(void (^)())callback;
|
||||
- (void)reloadDataAnimated:(BOOL)animated;
|
||||
- (void)reloadData;
|
||||
|
||||
// Setters
|
||||
- (void)setSelectedLineIndex:(NSInteger)selectedLineIndex animated:(BOOL)animated;
|
||||
|
||||
// Getters
|
||||
- (UIBezierPath *)bezierPathForLineChartLine:(JBLineChartLine *)lineChartLine filled:(BOOL)filled;
|
||||
- (JBShapeLayer *)shapeLayerForLineIndex:(NSUInteger)lineIndex filled:(BOOL)filled;
|
||||
- (JBGradientLayer *)gradientLayerForLineIndex:(NSUInteger)lineIndex filled:(BOOL)filled;
|
||||
|
||||
// Callback helpers
|
||||
- (void)fireCallback:(void (^)())callback;
|
||||
|
||||
@end
|
||||
|
||||
@protocol JBLineChartLinesViewDelegate <NSObject>
|
||||
|
||||
- (NSArray *)lineChartLinesForLineChartLinesView:(JBLineChartLinesView *)lineChartLinesView;
|
||||
- (CGFloat)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView dimmedSelectionOpacityAtLineIndex:(NSUInteger)lineIndex;
|
||||
- (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView colorForLineAtLineIndex:(NSUInteger)lineIndex;
|
||||
- (CAGradientLayer *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView gradientForLineAtLineIndex:(NSUInteger)lineIndex;
|
||||
- (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView fillColorForLineAtLineIndex:(NSUInteger)lineIndex;
|
||||
- (CAGradientLayer *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView fillGradientForLineAtLineIndex:(NSUInteger)lineIndex;
|
||||
- (CGFloat)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView widthForLineAtLineIndex:(NSUInteger)lineIndex;
|
||||
- (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView selectionColorForLineAtLineIndex:(NSUInteger)lineIndex;
|
||||
- (CAGradientLayer *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView selectionGradientForLineAtLineIndex:(NSUInteger)lineIndex;
|
||||
- (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView selectionFillColorForLineAtLineIndex:(NSUInteger)lineIndex;
|
||||
- (CAGradientLayer *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView selectionFillGradientForLineAtLineIndex:(NSUInteger)lineIndex;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,609 @@
|
||||
//
|
||||
// JBLineChartLinesView.m
|
||||
// JBChartViewDemo
|
||||
//
|
||||
// Created by Terry Worona on 12/26/15.
|
||||
// Copyright © 2015 Jawbone. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JBLineChartLinesView.h"
|
||||
|
||||
// Models
|
||||
#import "JBLineChartPoint.h"
|
||||
|
||||
// Numerics
|
||||
CGFloat const kJBLineChartLinesViewMiterLimit = -5.0;
|
||||
CGFloat const kJBLineChartLinesViewDefaultLinePhase = 1.0f;
|
||||
CGFloat const kJBLineChartLinesViewSmoothThresholdSlope = 0.01f;
|
||||
CGFloat const kJBLineChartLinesViewReloadDataAnimationDuration = 0.15f;
|
||||
NSInteger const kJBLineChartLinesViewSmoothThresholdVertical = 1;
|
||||
NSInteger const kJBLineChartLinesViewUnselectedLineIndex = -1;
|
||||
|
||||
// Structures
|
||||
static NSArray *kJBLineChartLinesViewDefaultDashPattern = nil;
|
||||
|
||||
@implementation JBLineChartLinesView
|
||||
|
||||
#pragma mark - Alloc/Init
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self == [JBLineChartLinesView class])
|
||||
{
|
||||
kJBLineChartLinesViewDefaultDashPattern = @[@(3), @(2)];
|
||||
}
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self)
|
||||
{
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Memory Management
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:self];
|
||||
}
|
||||
|
||||
#pragma mark - Drawing
|
||||
|
||||
- (void)drawRect:(CGRect)rect
|
||||
{
|
||||
[super drawRect:rect];
|
||||
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesForLineChartLinesView:)], @"JBLineChartLinesView // delegate must implement - (NSArray *)lineChartLinesForLineChartLinesView:(JBLineChartLinesView *)lineChartLinesView");
|
||||
NSArray *chartData = [self.delegate lineChartLinesForLineChartLinesView:self];
|
||||
|
||||
for (int lineIndex=0; lineIndex<[chartData count]; lineIndex++)
|
||||
{
|
||||
JBLineChartLine *lineChartLine = [chartData objectAtIndex:lineIndex];
|
||||
{
|
||||
UIBezierPath *linePath = [self bezierPathForLineChartLine:lineChartLine filled:NO];
|
||||
UIBezierPath *fillPath = [self bezierPathForLineChartLine:lineChartLine filled:YES];
|
||||
|
||||
if (linePath == nil || fillPath == nil)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
JBShapeLayer *shapeLayer = [self shapeLayerForLineIndex:lineIndex filled:NO];
|
||||
if (shapeLayer == nil)
|
||||
{
|
||||
shapeLayer = [[JBShapeLayer alloc] initWithTag:lineIndex filled:NO currentPath:linePath];
|
||||
}
|
||||
|
||||
JBShapeLayer *fillLayer = [self shapeLayerForLineIndex:lineIndex filled:YES];
|
||||
if (fillLayer == nil)
|
||||
{
|
||||
fillLayer = [[JBShapeLayer alloc] initWithTag:lineIndex filled:YES currentPath:nil]; // don't need currentPath since fill's aren't animatable (yet)
|
||||
}
|
||||
|
||||
shapeLayer.zPosition = 0.1f;
|
||||
shapeLayer.fillColor = [UIColor clearColor].CGColor;
|
||||
fillLayer.zPosition = 0.1f;
|
||||
fillLayer.fillColor = [UIColor clearColor].CGColor;
|
||||
|
||||
// Line style
|
||||
if (lineChartLine.lineStyle == JBLineChartViewLineStyleSolid)
|
||||
{
|
||||
shapeLayer.lineDashPhase = 0.0;
|
||||
shapeLayer.lineDashPattern = nil;
|
||||
}
|
||||
else if (lineChartLine.lineStyle == JBLineChartViewLineStyleDashed)
|
||||
{
|
||||
shapeLayer.lineDashPhase = kJBLineChartLinesViewDefaultLinePhase;
|
||||
shapeLayer.lineDashPattern = kJBLineChartLinesViewDefaultDashPattern;
|
||||
}
|
||||
|
||||
// Smoothing
|
||||
if (lineChartLine.smoothedLine)
|
||||
{
|
||||
if (lineChartLine.lineStyle == JBLineChartViewLineStyleDashed)
|
||||
{
|
||||
shapeLayer.lineCap = kCALineCapButt; // smoothed, dashed lines need butt caps
|
||||
}
|
||||
else
|
||||
{
|
||||
shapeLayer.lineCap = kCALineCapRound;
|
||||
}
|
||||
shapeLayer.lineJoin = kCALineJoinRound;
|
||||
fillLayer.lineCap = kCALineCapRound;
|
||||
fillLayer.lineJoin = kCALineJoinRound;
|
||||
}
|
||||
else
|
||||
{
|
||||
shapeLayer.lineCap = kCALineCapButt;
|
||||
shapeLayer.lineJoin = kCALineJoinMiter;
|
||||
fillLayer.lineCap = kCALineCapButt;
|
||||
fillLayer.lineJoin = kCALineJoinMiter;
|
||||
}
|
||||
|
||||
// Width
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:widthForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (CGFloat)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView widthForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
shapeLayer.lineWidth = [self.delegate lineChartLinesView:self widthForLineAtLineIndex:lineIndex];
|
||||
fillLayer.lineWidth = [self.delegate lineChartLinesView:self widthForLineAtLineIndex:lineIndex];
|
||||
|
||||
// Colors
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:colorForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView colorForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
shapeLayer.strokeColor = [self.delegate lineChartLinesView:self colorForLineAtLineIndex:lineIndex].CGColor;
|
||||
|
||||
// Line path
|
||||
shapeLayer.frame = self.bounds;
|
||||
if (self.animated)
|
||||
{
|
||||
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
|
||||
pathAnimation.fromValue = (id)shapeLayer.currentPath.CGPath;
|
||||
pathAnimation.toValue = (id)linePath.CGPath;
|
||||
pathAnimation.duration = kJBLineChartLinesViewReloadDataAnimationDuration;
|
||||
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:@"easeInEaseOut"];
|
||||
pathAnimation.fillMode = kCAFillModeBoth;
|
||||
pathAnimation.removedOnCompletion = NO;
|
||||
[shapeLayer addAnimation:pathAnimation forKey:@"shapeLayerPathAnimation"];
|
||||
}
|
||||
else
|
||||
{
|
||||
shapeLayer.path = linePath.CGPath;
|
||||
}
|
||||
shapeLayer.currentPath = [linePath copy];
|
||||
|
||||
// Fill path
|
||||
fillLayer.frame = self.bounds;
|
||||
fillLayer.path = fillPath.CGPath;
|
||||
|
||||
// Solid fill
|
||||
if (lineChartLine.fillColorStyle == JBLineChartViewColorStyleSolid)
|
||||
{
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:fillColorForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView fillColorForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
fillLayer.fillColor = [self.delegate lineChartLinesView:self fillColorForLineAtLineIndex:lineIndex].CGColor;
|
||||
[self.layer addSublayer:fillLayer];
|
||||
}
|
||||
|
||||
// Gradient fill
|
||||
else if (lineChartLine.fillColorStyle == JBLineChartViewColorStyleGradient)
|
||||
{
|
||||
JBGradientLayer *fillGradientLayer = [self gradientLayerForLineIndex:lineIndex filled:YES];
|
||||
if (fillGradientLayer == nil)
|
||||
{
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:fillGradientForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (CAGradientLayer *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView fillGradientForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
fillGradientLayer = [[JBGradientLayer alloc] initWithGradientLayer:[self.delegate lineChartLinesView:self fillGradientForLineAtLineIndex:lineIndex] tag:lineIndex filled:YES currentPath:nil];
|
||||
}
|
||||
fillGradientLayer.frame = fillLayer.frame;
|
||||
fillGradientLayer.mask = fillLayer;
|
||||
[self.layer addSublayer:fillGradientLayer];
|
||||
}
|
||||
|
||||
// Solid line
|
||||
if (lineChartLine.colorStyle == JBLineChartViewColorStyleSolid)
|
||||
{
|
||||
[self.layer addSublayer:shapeLayer];
|
||||
}
|
||||
|
||||
// Gradient line
|
||||
else if (lineChartLine.colorStyle == JBLineChartViewColorStyleGradient)
|
||||
{
|
||||
JBGradientLayer *gradientLayer = [self gradientLayerForLineIndex:lineIndex filled:NO];
|
||||
if (gradientLayer == nil)
|
||||
{
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:gradientForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (CAGradientLayer *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView gradientForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
gradientLayer = [[JBGradientLayer alloc] initWithGradientLayer:[self.delegate lineChartLinesView:self gradientForLineAtLineIndex:lineIndex] tag:lineIndex filled:NO currentPath:linePath];
|
||||
}
|
||||
gradientLayer.frame = shapeLayer.frame;
|
||||
|
||||
if (self.animated)
|
||||
{
|
||||
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
|
||||
pathAnimation.fromValue = (id)gradientLayer.currentPath.CGPath;
|
||||
pathAnimation.toValue = (id)linePath.CGPath;
|
||||
pathAnimation.duration = kJBLineChartLinesViewReloadDataAnimationDuration;
|
||||
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:@"easeInEaseOut"];
|
||||
pathAnimation.fillMode = kCAFillModeBoth;
|
||||
pathAnimation.removedOnCompletion = NO;
|
||||
[gradientLayer.mask addAnimation:pathAnimation forKey:@"gradientLayerMaskAnimation"];
|
||||
}
|
||||
else
|
||||
{
|
||||
gradientLayer.mask = shapeLayer;
|
||||
}
|
||||
gradientLayer.currentPath = [linePath copy];
|
||||
|
||||
[self.layer addSublayer:gradientLayer];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.animated = NO;
|
||||
}
|
||||
|
||||
#pragma mark - Data
|
||||
|
||||
- (void)reloadDataAnimated:(BOOL)animated callback:(void (^)())callback
|
||||
{
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesForLineChartLinesView:)], @"JBLineChartLinesView // delegate must implement - (NSArray *)lineChartLinesForLineChartLinesView:(JBLineChartLinesView *)lineChartLinesView");
|
||||
NSArray *chartData = [self.delegate lineChartLinesForLineChartLinesView:self];
|
||||
|
||||
NSUInteger lineCount = [chartData count];
|
||||
|
||||
__weak JBLineChartLinesView* weakSelf = self;
|
||||
|
||||
dispatch_block_t completionBlock = ^{
|
||||
weakSelf.animated = NO;
|
||||
[weakSelf setNeedsDisplay]; // re-draw layers
|
||||
if (callback)
|
||||
{
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
// Mark layers for animation or removal
|
||||
NSMutableArray *mutableRemovedLayers = [NSMutableArray array];
|
||||
for (CALayer *layer in [self.layer sublayers])
|
||||
{
|
||||
BOOL removeLayer = NO;
|
||||
|
||||
if ([layer isKindOfClass:[JBShapeLayer class]])
|
||||
{
|
||||
removeLayer = (((JBShapeLayer *)layer).tag >= lineCount);
|
||||
}
|
||||
else if ([layer isKindOfClass:[JBGradientLayer class]])
|
||||
{
|
||||
removeLayer = (((JBGradientLayer *)layer).tag >= lineCount);
|
||||
}
|
||||
|
||||
if (removeLayer)
|
||||
{
|
||||
[mutableRemovedLayers addObject:layer];
|
||||
}
|
||||
}
|
||||
|
||||
// Remove legacy layers
|
||||
NSArray *removedLayers = [NSArray arrayWithArray:mutableRemovedLayers];
|
||||
if ([removedLayers count] > 0)
|
||||
{
|
||||
for (int index=0; index<[removedLayers count]; index++)
|
||||
{
|
||||
CALayer *removedLayer = [removedLayers objectAtIndex:index];
|
||||
|
||||
if (animated)
|
||||
{
|
||||
[CATransaction begin];
|
||||
{
|
||||
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
|
||||
animation.fromValue = [NSNumber numberWithFloat:1.0f];
|
||||
animation.toValue = [NSNumber numberWithFloat:0.0f];
|
||||
animation.duration = kJBLineChartLinesViewReloadDataAnimationDuration;
|
||||
animation.timingFunction = [CAMediaTimingFunction functionWithName:@"easeInEaseOut"];
|
||||
animation.fillMode = kCAFillModeBoth;
|
||||
animation.removedOnCompletion = NO;
|
||||
|
||||
[CATransaction setCompletionBlock:^{
|
||||
[removedLayer removeFromSuperlayer];
|
||||
if (index == [removedLayers count]-1)
|
||||
{
|
||||
completionBlock();
|
||||
}
|
||||
}];
|
||||
|
||||
[removedLayer addAnimation:animation forKey:@"removeShapeLayerAnimation"];
|
||||
}
|
||||
[CATransaction commit];
|
||||
}
|
||||
else
|
||||
{
|
||||
[removedLayer removeFromSuperlayer];
|
||||
if (index == [removedLayers count]-1)
|
||||
{
|
||||
completionBlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
completionBlock();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reloadDataAnimated:(BOOL)animated
|
||||
{
|
||||
[self reloadDataAnimated:animated callback:nil];
|
||||
}
|
||||
|
||||
- (void)reloadData
|
||||
{
|
||||
[self reloadDataAnimated:NO];
|
||||
}
|
||||
|
||||
#pragma mark - Setters
|
||||
|
||||
- (void)setSelectedLineIndex:(NSInteger)selectedLineIndex animated:(BOOL)animated
|
||||
{
|
||||
_selectedLineIndex = selectedLineIndex;
|
||||
|
||||
__weak JBLineChartLinesView* weakSelf = self;
|
||||
|
||||
dispatch_block_t adjustLines = ^{
|
||||
NSMutableArray *layersToReplace = [NSMutableArray array];
|
||||
|
||||
NSString * const oldLayerKey = @"oldLayer";
|
||||
NSString * const newLayerKey = @"newLayer";
|
||||
|
||||
for (CALayer *layer in [weakSelf.layer sublayers])
|
||||
{
|
||||
/*
|
||||
* Solid line or fill
|
||||
*/
|
||||
if ([layer isKindOfClass:[JBShapeLayer class]])
|
||||
{
|
||||
JBShapeLayer *shapeLayer = (JBShapeLayer * )layer;
|
||||
|
||||
if (shapeLayer.filled)
|
||||
{
|
||||
// Selected solid fill
|
||||
if (shapeLayer.tag == weakSelf.selectedLineIndex)
|
||||
{
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:selectionFillColorForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView selectionFillColorForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
shapeLayer.fillColor = [self.delegate lineChartLinesView:self selectionFillColorForLineAtLineIndex:shapeLayer.tag].CGColor;
|
||||
shapeLayer.opacity = 1.0f;
|
||||
}
|
||||
// Unselected solid fill
|
||||
else
|
||||
{
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:fillColorForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView fillColorForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
shapeLayer.fillColor = [self.delegate lineChartLinesView:self fillColorForLineAtLineIndex:shapeLayer.tag].CGColor;
|
||||
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:dimmedSelectionOpacityAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (CGFloat)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView dimmedSelectionOpacityAtLineIndex:(NSUInteger)lineIndex");
|
||||
shapeLayer.opacity = (weakSelf.selectedLineIndex == kJBLineChartLinesViewUnselectedLineIndex) ? 1.0f : [self.delegate lineChartLinesView:self dimmedSelectionOpacityAtLineIndex:shapeLayer.tag];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Selected solid line
|
||||
if (shapeLayer.tag == weakSelf.selectedLineIndex)
|
||||
{
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:selectionColorForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView selectionColorForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
shapeLayer.strokeColor = [self.delegate lineChartLinesView:self selectionColorForLineAtLineIndex:shapeLayer.tag].CGColor;
|
||||
shapeLayer.opacity = 1.0f;
|
||||
}
|
||||
// Unselected solid line
|
||||
else
|
||||
{
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:colorForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView colorForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
shapeLayer.strokeColor = [self.delegate lineChartLinesView:self colorForLineAtLineIndex:shapeLayer.tag].CGColor;
|
||||
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:dimmedSelectionOpacityAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (CGFloat)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView dimmedSelectionOpacityAtLineIndex:(NSUInteger)lineIndex");
|
||||
shapeLayer.opacity = (weakSelf.selectedLineIndex == kJBLineChartLinesViewUnselectedLineIndex) ? 1.0f : [self.delegate lineChartLinesView:self dimmedSelectionOpacityAtLineIndex:shapeLayer.tag];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Gradient line or fill
|
||||
*/
|
||||
else if ([layer isKindOfClass:[CAGradientLayer class]])
|
||||
{
|
||||
CAGradientLayer *gradientLayer = (CAGradientLayer * )layer;
|
||||
|
||||
if ([gradientLayer.mask isKindOfClass:[JBShapeLayer class]])
|
||||
{
|
||||
JBShapeLayer *shapeLayer = (JBShapeLayer * )gradientLayer.mask;
|
||||
|
||||
if (shapeLayer.filled)
|
||||
{
|
||||
// Selected gradient fill
|
||||
if (shapeLayer.tag == weakSelf.selectedLineIndex)
|
||||
{
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:selectionFillGradientForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (CAGradientLayer *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView selectionFillGradientForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
CAGradientLayer *selectedFillGradient = [self.delegate lineChartLinesView:self selectionFillGradientForLineAtLineIndex:shapeLayer.tag];
|
||||
selectedFillGradient.frame = layer.frame;
|
||||
selectedFillGradient.mask = layer.mask;
|
||||
selectedFillGradient.opacity = 1.0f;
|
||||
[layersToReplace addObject:@{oldLayerKey: layer, newLayerKey: selectedFillGradient}];
|
||||
}
|
||||
// Unselected gradient fill
|
||||
else
|
||||
{
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:fillGradientForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (CAGradientLayer *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView fillGradientForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
CAGradientLayer *unselectedFillGradient = [self.delegate lineChartLinesView:self fillGradientForLineAtLineIndex:shapeLayer.tag];
|
||||
unselectedFillGradient.frame = layer.frame;
|
||||
unselectedFillGradient.mask = layer.mask;
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:dimmedSelectionOpacityAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (CGFloat)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView dimmedSelectionOpacityAtLineIndex:(NSUInteger)lineIndex");
|
||||
unselectedFillGradient.opacity = (weakSelf.selectedLineIndex == kJBLineChartLinesViewUnselectedLineIndex) ? 1.0f : [self.delegate lineChartLinesView:self dimmedSelectionOpacityAtLineIndex:shapeLayer.tag];
|
||||
[layersToReplace addObject:@{oldLayerKey: layer, newLayerKey: unselectedFillGradient}];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Selected gradient line
|
||||
if (shapeLayer.tag == weakSelf.selectedLineIndex)
|
||||
{
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:selectionGradientForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (CAGradientLayer *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView selectionGradientForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
CAGradientLayer *selectedGradient = [self.delegate lineChartLinesView:self selectionGradientForLineAtLineIndex:shapeLayer.tag];
|
||||
selectedGradient.frame = layer.frame;
|
||||
selectedGradient.mask = layer.mask;
|
||||
selectedGradient.opacity = 1.0f;
|
||||
[layersToReplace addObject:@{oldLayerKey: layer, newLayerKey: selectedGradient}];
|
||||
}
|
||||
// Unselected gradient line
|
||||
else
|
||||
{
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:gradientForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (CAGradientLayer *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView gradientForLineAtLineIndex:(NSUInteger)lineIndex");
|
||||
CAGradientLayer *unselectedGradient = [self.delegate lineChartLinesView:self gradientForLineAtLineIndex:shapeLayer.tag];
|
||||
unselectedGradient.frame = layer.frame;
|
||||
unselectedGradient.mask = layer.mask;
|
||||
NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:dimmedSelectionOpacityAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (CGFloat)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView dimmedSelectionOpacityAtLineIndex:(NSUInteger)lineIndex");
|
||||
shapeLayer.opacity = (weakSelf.selectedLineIndex == kJBLineChartLinesViewUnselectedLineIndex) ? 1.0f : [self.delegate lineChartLinesView:self dimmedSelectionOpacityAtLineIndex:shapeLayer.tag];
|
||||
[layersToReplace addObject:@{oldLayerKey: layer, newLayerKey: unselectedGradient}];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (NSDictionary *layerPair in layersToReplace)
|
||||
{
|
||||
[weakSelf.layer replaceSublayer:layerPair[oldLayerKey] with:layerPair[newLayerKey]];
|
||||
}
|
||||
};
|
||||
|
||||
if (animated)
|
||||
{
|
||||
[UIView animateWithDuration:kJBChartViewDefaultAnimationDuration animations:^{
|
||||
adjustLines();
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
adjustLines();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setSelectedLineIndex:(NSInteger)selectedLineIndex
|
||||
{
|
||||
[self setSelectedLineIndex:selectedLineIndex animated:NO];
|
||||
}
|
||||
|
||||
#pragma mark - Getters
|
||||
|
||||
- (UIBezierPath *)bezierPathForLineChartLine:(JBLineChartLine *)lineChartLine filled:(BOOL)filled
|
||||
{
|
||||
if ([lineChartLine.lineChartPoints count] > 0)
|
||||
{
|
||||
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
|
||||
|
||||
bezierPath.miterLimit = kJBLineChartLinesViewMiterLimit;
|
||||
|
||||
JBLineChartPoint *previousLineChartPoint = nil;
|
||||
CGFloat previousSlope = 0.0f;
|
||||
|
||||
BOOL visiblePointFound = NO;
|
||||
NSArray *sortedLineChartPoints = [lineChartLine.lineChartPoints sortedArrayUsingSelector:@selector(compare:)];
|
||||
CGFloat firstXPosition = 0.0f;
|
||||
CGFloat firstYPosition = 0.0f;
|
||||
CGFloat lastXPosition = 0.0f;
|
||||
CGFloat lastYPosition = 0.0f;
|
||||
|
||||
for (NSUInteger index=0; index<[sortedLineChartPoints count]; index++)
|
||||
{
|
||||
JBLineChartPoint *lineChartPoint = [sortedLineChartPoints objectAtIndex:index];
|
||||
|
||||
if (lineChartPoint.hidden)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!visiblePointFound)
|
||||
{
|
||||
[bezierPath moveToPoint:CGPointMake(lineChartPoint.position.x, lineChartPoint.position.y)];
|
||||
firstXPosition = lineChartPoint.position.x;
|
||||
firstYPosition = lineChartPoint.position.y;
|
||||
visiblePointFound = YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
JBLineChartPoint *nextLineChartPoint = nil;
|
||||
if (index != ([lineChartLine.lineChartPoints count] - 1))
|
||||
{
|
||||
nextLineChartPoint = [sortedLineChartPoints objectAtIndex:(index + 1)];
|
||||
}
|
||||
|
||||
CGFloat nextSlope = (nextLineChartPoint != nil) ? ((nextLineChartPoint.position.y - lineChartPoint.position.y)) / ((nextLineChartPoint.position.x - lineChartPoint.position.x)) : previousSlope;
|
||||
CGFloat currentSlope = ((lineChartPoint.position.y - previousLineChartPoint.position.y)) / (lineChartPoint.position.x-previousLineChartPoint.position.x);
|
||||
|
||||
BOOL deltaFromNextSlope = ((currentSlope >= (nextSlope + kJBLineChartLinesViewSmoothThresholdSlope)) || (currentSlope <= (nextSlope - kJBLineChartLinesViewSmoothThresholdSlope)));
|
||||
BOOL deltaFromPreviousSlope = ((currentSlope >= (previousSlope + kJBLineChartLinesViewSmoothThresholdSlope)) || (currentSlope <= (previousSlope - kJBLineChartLinesViewSmoothThresholdSlope)));
|
||||
BOOL deltaFromPreviousY = (lineChartPoint.position.y >= previousLineChartPoint.position.y + kJBLineChartLinesViewSmoothThresholdVertical) || (lineChartPoint.position.y <= previousLineChartPoint.position.y - kJBLineChartLinesViewSmoothThresholdVertical);
|
||||
|
||||
if (lineChartLine.smoothedLine && deltaFromNextSlope && deltaFromPreviousSlope && deltaFromPreviousY)
|
||||
{
|
||||
CGFloat deltaX = lineChartPoint.position.x - previousLineChartPoint.position.x;
|
||||
CGFloat controlPointX = previousLineChartPoint.position.x + (deltaX / 2);
|
||||
|
||||
CGPoint controlPoint1 = CGPointMake(controlPointX, previousLineChartPoint.position.y);
|
||||
CGPoint controlPoint2 = CGPointMake(controlPointX, lineChartPoint.position.y);
|
||||
|
||||
[bezierPath addCurveToPoint:CGPointMake(lineChartPoint.position.x, lineChartPoint.position.y) controlPoint1:controlPoint1 controlPoint2:controlPoint2];
|
||||
}
|
||||
else
|
||||
{
|
||||
[bezierPath addLineToPoint:CGPointMake(lineChartPoint.position.x, lineChartPoint.position.y)];
|
||||
}
|
||||
|
||||
lastXPosition = lineChartPoint.position.x;
|
||||
lastYPosition = lineChartPoint.position.y;
|
||||
previousSlope = currentSlope;
|
||||
}
|
||||
previousLineChartPoint = lineChartPoint;
|
||||
}
|
||||
|
||||
if (filled)
|
||||
{
|
||||
UIBezierPath *filledBezierPath = [bezierPath copy];
|
||||
|
||||
if(visiblePointFound)
|
||||
{
|
||||
[filledBezierPath addLineToPoint:CGPointMake(lastXPosition, lastYPosition)];
|
||||
[filledBezierPath addLineToPoint:CGPointMake(lastXPosition, self.bounds.size.height)];
|
||||
|
||||
[filledBezierPath addLineToPoint:CGPointMake(firstXPosition, self.bounds.size.height)];
|
||||
[filledBezierPath addLineToPoint:CGPointMake(firstXPosition, firstYPosition)];
|
||||
}
|
||||
|
||||
return filledBezierPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
return bezierPath;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (JBShapeLayer *)shapeLayerForLineIndex:(NSUInteger)lineIndex filled:(BOOL)filled
|
||||
{
|
||||
for (CALayer *layer in [self.layer sublayers])
|
||||
{
|
||||
if ([layer isKindOfClass:[JBShapeLayer class]])
|
||||
{
|
||||
if (((JBShapeLayer *)layer).tag == lineIndex && ((JBShapeLayer *)layer).filled == filled)
|
||||
{
|
||||
return (JBShapeLayer *)layer;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (JBGradientLayer *)gradientLayerForLineIndex:(NSUInteger)lineIndex filled:(BOOL)filled
|
||||
{
|
||||
for (CALayer *layer in [self.layer sublayers])
|
||||
{
|
||||
if ([layer isKindOfClass:[JBGradientLayer class]])
|
||||
{
|
||||
if (((JBGradientLayer *)layer).tag == lineIndex && ((JBGradientLayer *)layer).filled == filled)
|
||||
{
|
||||
return (JBGradientLayer *)layer;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark - Callback Helpers
|
||||
|
||||
- (void)fireCallback:(void (^)())callback
|
||||
{
|
||||
dispatch_block_t callbackCopy = [callback copy];
|
||||
|
||||
if (callbackCopy != nil)
|
||||
{
|
||||
callbackCopy();
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user