Created
May 15, 2019 07:58
-
-
Save jazzz13/8759867a9225c8dfe8dc2382eeb29a80 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// FAMBarChartDrawLayer.m | |
// frank-ios | |
// | |
// Created by Ринат Муртазин on 08/12/15. | |
// Copyright © 2015 Frank. All rights reserved. | |
// | |
#import "FABarChartDrawLayer.h" | |
#import "FAMBarChartValues.h" | |
#import "FAMBarChartValue.h" | |
#import "FABarChartView.h" | |
#import "FAUIHelper.h" | |
#import <CoreText/CoreText.h> | |
#import "RSTimingFunction.h" | |
#define MAX_BAR_HEIGHT 65.f | |
#define BAR_CORNER_RADIUS 2.f | |
#define DEFAULT_SIDE_PADDING 14.f | |
#define BARS_Y_ANIMATION_SHIFT 10.f | |
#define LEGEND_Y_ANIMATION_SHIFT 15.f | |
#define BAR_AND_LEGEND_ANIMATION_DURATION 1.f | |
@implementation FABarChartDrawLayer | |
{ | |
CGFloat _barWidth; | |
CGFloat _legendWidth; | |
CGFloat _gap; | |
CGFloat _minBarHeight; | |
CGFloat _startX; | |
UIColor *_legendColor; | |
NSArray *_incomeColors; | |
NSArray *_expenseColors; | |
BOOL _drawLabelInCenter; | |
BOOL _drawLabelHalfWidth; | |
BOOL _drawLabelMonthLegend; | |
RSTimingFunction *_timingFunction; | |
CGFloat _delayBarAnimation; | |
CGFloat _delayLegendAnimation; | |
__weak FABarChartView *_barChartView; | |
} | |
#pragma mark - Init | |
- (instancetype)initWithBarView:(FABarChartView *)barChartView | |
{ | |
self = [super init]; | |
if(self) | |
{ | |
_barChartView = barChartView; | |
_gap = [FAUIHelper pixelsToPoints:4]; | |
_minBarHeight = [FAUIHelper pixelsToPoints:8]; | |
_legendColor = [UIColor colorFromHex:@"647178"]; | |
// 0: zero value color | |
// 4: last bar color | |
// TODO: ВЫНЕСТИ ЭТО В СТИЛЕВОЙ КОМПОНЕНТ!! | |
_incomeColors = @[ | |
[UIColor colorFromHex:@"166844"], | |
[UIColor colorFromHex:@"02a25c"], | |
[UIColor colorFromHex:@"17c868"], | |
[UIColor colorFromHex:@"27d35d"], | |
[UIColor colorFromHex:@"1ddb44"] | |
]; | |
_expenseColors = @[ | |
[UIColor colorFromHex:@"4c656f"], | |
[UIColor colorFromHex:@"5f6e75"], | |
[UIColor colorFromHex:@"717d82"], | |
[UIColor colorFromHex:@"8c9599"], | |
[UIColor colorFromHex:@"a7aeb1"] | |
]; | |
self.contentsScale = [UIScreen mainScreen].scale; | |
_timingFunction = [RSTimingFunction timingFunctionWithControlPoint1:CGPointMake(0.f, 0.6f) | |
controlPoint2:CGPointMake(0.2f, 1.f)]; | |
} | |
return self; | |
} | |
- (instancetype)initWithLayer:(FABarChartDrawLayer *)layer | |
{ | |
self = [super initWithLayer:layer]; | |
if(self) | |
{ | |
_barWidth = layer->_barWidth; | |
_legendWidth = layer->_legendWidth; | |
_gap = layer->_gap; | |
_minBarHeight = layer->_minBarHeight; | |
_startX = layer->_startX; | |
_legendColor = layer->_legendColor; | |
_incomeColors = layer->_incomeColors; | |
_expenseColors = layer->_expenseColors; | |
_drawLabelInCenter = layer->_drawLabelInCenter; | |
_drawLabelHalfWidth = layer->_drawLabelHalfWidth; | |
_drawLabelMonthLegend = layer->_drawLabelMonthLegend; | |
_barChartView = layer->_barChartView; | |
self.contentsScale = layer.contentsScale; | |
_timingFunction = layer->_timingFunction; | |
_delayBarAnimation = layer->_delayBarAnimation; | |
_delayLegendAnimation = layer->_delayLegendAnimation; | |
} | |
return self; | |
} | |
- (void)calculateOptions | |
{ | |
NSInteger count = [_barChartView.values.values count]; | |
CGFloat width = self.frame.size.width; | |
CGFloat scale = [UIScreen mainScreen].scale; | |
CGFloat barWidthPixels = width * scale - 2 * DEFAULT_SIDE_PADDING * scale; | |
barWidthPixels += _gap * scale; | |
barWidthPixels /= count; | |
barWidthPixels -= _gap * scale; | |
_barWidth = [FAUIHelper pixelsToPoints:barWidthPixels]; | |
_startX = width - (_barWidth + _gap) * count; | |
_startX += _gap; | |
_startX /= 2; | |
_startX = [FAUIHelper roundToResolution:_startX]; | |
_delayBarAnimation = (_appearAnimationDuration - BAR_AND_LEGEND_ANIMATION_DURATION) / (float)(count - 1); | |
// options for legend | |
_legendWidth = width - 2 * _startX; | |
_legendWidth /= [[_barChartView.values legendList] count]; | |
_legendWidth = [FAUIHelper roundToResolution:_legendWidth]; | |
_delayLegendAnimation = (_appearAnimationDuration - BAR_AND_LEGEND_ANIMATION_DURATION); | |
_delayLegendAnimation /= (float)([[_barChartView.values legendList] count] - 1); | |
EBarChartValueInterval interval = _barChartView.values.persistentIntervalValue; | |
if(interval == EBarChartValueIntervalWeek) | |
{ | |
_drawLabelHalfWidth = NO; | |
_drawLabelInCenter = YES; | |
} | |
else if (interval == EBarChartValueIntervalTwoWeeks | |
|| interval == EBarChartValueIntervalYear | |
|| interval == EBarChartValueIntervalTwelveMonths) | |
{ | |
_drawLabelHalfWidth = YES; | |
_drawLabelInCenter = YES; | |
} | |
else if (interval == EBarChartValueIntervalMonth) | |
{ | |
_drawLabelHalfWidth = NO; | |
_drawLabelInCenter = YES; | |
_drawLabelMonthLegend = YES; | |
barWidthPixels = width * scale - 2 * DEFAULT_SIDE_PADDING * scale; | |
barWidthPixels += _gap * scale; | |
barWidthPixels /= 31; // harcode fior month | |
barWidthPixels -= _gap * scale; | |
_legendWidth = 5 * ([FAUIHelper pixelsToPoints:floorf(barWidthPixels)] + _gap); | |
} | |
else if (interval == EBarChartValueIntervalThreeMonths | |
|| interval == EBarChartValueIntervalSixMonths | |
|| interval == EBarChartValueIntervalTwoYears | |
|| interval == EBarChartValueIntervalFiveYears) | |
{ | |
_drawLabelHalfWidth = NO; | |
_drawLabelInCenter = NO; | |
} | |
} | |
#pragma mark - Overwritten | |
+ (BOOL)needsDisplayForKey:(NSString *)key | |
{ | |
if([key isEqualToString:@"appearAnimationDuration"]) | |
{ | |
return YES; | |
} | |
return [super needsDisplayForKey:key]; | |
} | |
#pragma mark Drawing | |
- (void)drawInContext:(CGContextRef)context | |
{ | |
if(self.modelLayer == self) | |
{ | |
[self calculateOptions]; | |
} | |
CGContextSetLineWidth(context, 2 * PIXEL); | |
CGFloat x = _startX; | |
CGFloat height = 0; | |
NSInteger count = [_barChartView.values.values count]; | |
for(int i = 0; i < count; i++) | |
{ | |
FAMBarChartValue *value = [_barChartView.values.values objectAtIndex:i]; | |
CGFloat animationValue = [self animValueForBar:i]; | |
CGContextSetFillColorWithColor(context, [[self colorForValue:value] colorWithAlphaComponent:animationValue].CGColor); | |
height = MAX_BAR_HEIGHT * value.valueValue * animationValue; | |
height = MAX(height, _minBarHeight); | |
if(self.modelLayer == self) | |
{ | |
height = [FAUIHelper roundToResolution:height]; | |
} | |
CGFloat y = MAX_BAR_HEIGHT - height + BARS_Y_ANIMATION_SHIFT * (1 - animationValue); | |
// get our rounded rect as a path | |
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake( | |
x, | |
y, | |
_barWidth, | |
height | |
) | |
cornerRadius:BAR_CORNER_RADIUS]; | |
// draw the path | |
CGContextAddPath(context, path.CGPath); | |
CGContextDrawPath(context, kCGPathFill); | |
// stroke | |
if(value.colorLevelValue > 0) | |
{ | |
UIBezierPath *strokePath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake( | |
x + PIXEL, | |
y + PIXEL, | |
_barWidth - 2*PIXEL, | |
height - 2*PIXEL | |
) | |
cornerRadius:BAR_CORNER_RADIUS]; | |
CGContextAddPath(context, strokePath.CGPath); | |
CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] colorWithAlphaComponent:animationValue * 0.11f].CGColor); | |
CGContextDrawPath(context, kCGPathStroke); | |
} | |
x += _barWidth + _gap; | |
} | |
[self drawLegend:context]; | |
} | |
- (void)drawLegend:(CGContextRef)context | |
{ | |
// flip the coordinate system | |
CGContextSetTextMatrix(context, CGAffineTransformIdentity); | |
CGContextTranslateCTM(context, 0, self.bounds.size.height); | |
CGContextScaleCTM(context, 1.0, -1.0); | |
CGContextSetShouldSmoothFonts(context, YES); | |
CGContextSetShouldAntialias(context, YES); | |
CGFloat legendHeight = self.frame.size.height - MAX_BAR_HEIGHT; | |
CGFloat legendY = -6.f; | |
CGFloat legendWidth = _drawLabelHalfWidth ? [FAUIHelper roundToResolution:_legendWidth / 2.f] : _legendWidth ; | |
CGFloat x = _startX; | |
// exception for month | |
if(_drawLabelMonthLegend) | |
{ | |
x -= [FAUIHelper roundToResolution:_legendWidth * 2 / 5.f]; | |
} | |
if(_drawLabelInCenter == NO) | |
{ | |
x += _gap; | |
} | |
else | |
{ | |
x -= [FAUIHelper roundToResolution:_gap / 2.f]; | |
} | |
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new]; | |
paragraphStyle.alignment = _drawLabelInCenter ? NSTextAlignmentCenter : NSTextAlignmentLeft; | |
NSMutableDictionary *attributes = @{ | |
NSForegroundColorAttributeName : _legendColor, | |
NSFontAttributeName : [UIFont fontWithSize:10.f], | |
NSParagraphStyleAttributeName : paragraphStyle | |
}.mutableCopy; | |
NSArray *legendList = [_barChartView.values legendList]; | |
for(int i = 0; i < [legendList count]; i++) | |
{ | |
CGFloat animationValue = [self animValueForLegend:i]; | |
if(animationValue < 1) | |
{ | |
[attributes setObject:[_legendColor colorWithAlphaComponent:animationValue] | |
forKey:NSForegroundColorAttributeName]; | |
} | |
NSAttributedString *text = [[NSAttributedString alloc] initWithString:legendList[i] | |
attributes:attributes]; | |
CGRect rect = CGRectMake(x, | |
legendY - LEGEND_Y_ANIMATION_SHIFT * (1.f - animationValue), | |
legendWidth, | |
legendHeight); | |
if(self.modelLayer != self) | |
{ | |
CGFloat nextPixel = ABS(rect.origin.y - [FAUIHelper roundToResolution:rect.origin.y]); | |
NSMutableDictionary *tempAttributtes = [NSMutableDictionary dictionaryWithDictionary:attributes]; | |
[tempAttributtes setObject:[_legendColor colorWithAlphaComponent:animationValue * (0.3f * nextPixel / PIXEL)] | |
forKey:NSForegroundColorAttributeName]; | |
CGRect tempRect = rect; | |
tempRect.origin.y = [FAUIHelper roundToResolution:tempRect.origin.y + PIXEL]; | |
[self drawText:[[NSAttributedString alloc] initWithString:legendList[i] | |
attributes:tempAttributtes] | |
ctx:context | |
frame:tempRect]; | |
} | |
[self drawText:text | |
ctx:context | |
frame:[FAUIHelper roundFrameToResolution:rect]]; | |
x += _legendWidth; | |
// exception for month | |
if(_drawLabelMonthLegend | |
&& i == 0) | |
{ | |
x -= [FAUIHelper roundToResolution:_legendWidth / 5.f]; | |
} | |
} | |
} | |
- (void)drawText:(NSAttributedString *)text | |
ctx:(CGContextRef)ctx | |
frame:(CGRect)rect | |
{ | |
CGMutablePathRef path = CGPathCreateMutable(); | |
CGPathAddRect(path, NULL, rect); | |
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)text); | |
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, [text length]), path, NULL); | |
CTFrameDraw(frame, ctx); | |
CFRelease(frame); | |
CFRelease(path); | |
CFRelease(framesetter); | |
} | |
- (UIColor *)colorForValue:(FAMBarChartValue *)value | |
{ | |
NSArray *colors = value.isIncomeValue ? _incomeColors : _expenseColors ; | |
return colors[value.colorLevelValue]; | |
} | |
- (CGFloat)animValueForBar:(int)index | |
{ | |
if(self.modelLayer == self) | |
{ | |
return 1.f; | |
} | |
CGFloat result = _appearAnimationDuration - _delayBarAnimation * index; | |
result = MAX(0, MIN(1, result)); | |
return [_timingFunction valueForX:result]; | |
} | |
- (CGFloat)animValueForLegend:(int)index | |
{ | |
if(self.modelLayer == self) | |
{ | |
return 1.f; | |
} | |
CGFloat result = _appearAnimationDuration - _delayLegendAnimation * index; | |
result = MAX(0, MIN(1, result)); | |
return [_timingFunction valueForX:result]; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment