Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
//
// 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
You can’t perform that action at this time.