From cd469f070a27313fb04a780d711af7f719b9e9cc Mon Sep 17 00:00:00 2001 From: Ramsundar Shandilya Date: Mon, 27 Jul 2015 14:50:05 +0530 Subject: [PATCH] Implement Stacked Bar Graphs. Refactor other graph components. --- .../APCAppCore.xcodeproj/project.pbxproj | 15 +- .../Components/GraphCharts/APCBarGraphView.h | 86 ++ .../Components/GraphCharts/APCBarGraphView.m | 993 ++++++++++++++++++ .../Components/GraphCharts/APCBaseGraphView.h | 7 +- .../Components/GraphCharts/APCBaseGraphView.m | 93 +- .../GraphCharts/APCDiscreteGraphView.h | 5 +- .../GraphCharts/APCDiscreteGraphView.m | 106 +- .../UI/Components/GraphCharts/APCGraph.h | 1 + .../GraphCharts/APCGraphConstants.h | 6 +- .../Components/GraphCharts/APCLineGraphView.h | 5 +- .../Components/GraphCharts/APCLineGraphView.m | 47 +- .../APCNormalDistributionGraphView.m | 2 +- 12 files changed, 1225 insertions(+), 141 deletions(-) create mode 100644 APCAppCore/APCAppCore/UI/Components/GraphCharts/APCBarGraphView.h create mode 100644 APCAppCore/APCAppCore/UI/Components/GraphCharts/APCBarGraphView.m diff --git a/APCAppCore/APCAppCore.xcodeproj/project.pbxproj b/APCAppCore/APCAppCore.xcodeproj/project.pbxproj index fc1ab74b..5f078b0f 100644 --- a/APCAppCore/APCAppCore.xcodeproj/project.pbxproj +++ b/APCAppCore/APCAppCore.xcodeproj/project.pbxproj @@ -221,6 +221,8 @@ 5B09ED201ABC1BEF003C5061 /* APCLicenseInfoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B09ED1E1ABC1BEF003C5061 /* APCLicenseInfoViewController.m */; }; 5B234E141A329BF300A5A3A0 /* APCWithdrawDescriptionViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 5B234E121A329BF300A5A3A0 /* APCWithdrawDescriptionViewController.h */; }; 5B234E151A329BF300A5A3A0 /* APCWithdrawDescriptionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B234E131A329BF300A5A3A0 /* APCWithdrawDescriptionViewController.m */; }; + 5B2CD8601B66184B00DF8E08 /* APCBarGraphView.h in Headers */ = {isa = PBXBuildFile; fileRef = 5B2CD85E1B66184B00DF8E08 /* APCBarGraphView.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5B2CD8611B66184B00DF8E08 /* APCBarGraphView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B2CD85F1B66184B00DF8E08 /* APCBarGraphView.m */; }; 5B4574CA1ABFD07A00601DCC /* License_BridgeSDK.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B4574C71ABFD07A00601DCC /* License_BridgeSDK.txt */; }; 5B4574CC1ABFD07A00601DCC /* License_ZipZap.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B4574C91ABFD07A00601DCC /* License_ZipZap.txt */; }; 5B534ED41B2179550049C6AB /* APCNewsFeedManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 5B534ED21B2179550049C6AB /* APCNewsFeedManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -985,6 +987,8 @@ 5B09ED1E1ABC1BEF003C5061 /* APCLicenseInfoViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = APCLicenseInfoViewController.m; sourceTree = ""; }; 5B234E121A329BF300A5A3A0 /* APCWithdrawDescriptionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = APCWithdrawDescriptionViewController.h; sourceTree = ""; }; 5B234E131A329BF300A5A3A0 /* APCWithdrawDescriptionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = APCWithdrawDescriptionViewController.m; sourceTree = ""; }; + 5B2CD85E1B66184B00DF8E08 /* APCBarGraphView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = APCBarGraphView.h; sourceTree = ""; }; + 5B2CD85F1B66184B00DF8E08 /* APCBarGraphView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = APCBarGraphView.m; sourceTree = ""; }; 5B4574C71ABFD07A00601DCC /* License_BridgeSDK.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = License_BridgeSDK.txt; sourceTree = ""; }; 5B4574C91ABFD07A00601DCC /* License_ZipZap.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = License_ZipZap.txt; sourceTree = ""; }; 5B534ED21B2179550049C6AB /* APCNewsFeedManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = APCNewsFeedManager.h; sourceTree = ""; }; @@ -2804,13 +2808,15 @@ F5F1292F1A2F78490015982C /* GraphCharts */ = { isa = PBXGroup; children = ( + F5F129341A2F78490015982C /* APCGraph.h */, + 5BD6EBB31A9C77D900C3BFB0 /* APCBaseGraphView.h */, + 5BD6EBB41A9C77D900C3BFB0 /* APCBaseGraphView.m */, + 5B2CD85E1B66184B00DF8E08 /* APCBarGraphView.h */, + 5B2CD85F1B66184B00DF8E08 /* APCBarGraphView.m */, 5B7D47181B0549FA00958921 /* APCCubicCurveAlgorithm.h */, 5B7D47191B0549FA00958921 /* APCCubicCurveAlgorithm.m */, 5B7D471A1B0549FA00958921 /* APCNormalDistributionGraphView.h */, 5B7D471B1B0549FA00958921 /* APCNormalDistributionGraphView.m */, - F5F129341A2F78490015982C /* APCGraph.h */, - 5BD6EBB31A9C77D900C3BFB0 /* APCBaseGraphView.h */, - 5BD6EBB41A9C77D900C3BFB0 /* APCBaseGraphView.m */, 5BD6EBB51A9C77D900C3BFB0 /* APCDiscreteGraphView.h */, 5BD6EBB61A9C77D900C3BFB0 /* APCDiscreteGraphView.m */, F5F129361A2F78490015982C /* APCLineGraphView.h */, @@ -3495,6 +3501,7 @@ 5B7D471E1B0549FA00958921 /* APCNormalDistributionGraphView.h in Headers */, CFFDED701A95731F00B25581 /* APCMedicationTrackerCalendarViewController.h in Headers */, 369E28081A96B7A200D35DFA /* APCMedTrackerMedication+Helper.h in Headers */, + 5B2CD8601B66184B00DF8E08 /* APCBarGraphView.h in Headers */, F5F12A021A2F78490015982C /* APCScheduledTask.h in Headers */, 369E28041A96B7A200D35DFA /* APCMedTrackerInflatableItem+Helper.h in Headers */, F5B947C11A73272C0034C522 /* NSDictionary+APCAdditions.h in Headers */, @@ -3895,7 +3902,6 @@ F5B9480D1A73272C0034C522 /* APCScheduleExpressionTokenizer.m in Sources */, 6C7346091A7ECD3B00DA9CD8 /* APCConsentTaskViewController.m in Sources */, EE64F4321B02517000A402A7 /* APCOnboardingManager.m in Sources */, - F54DDD281A8026010073E4B4 /* APCDataTracker.m in Sources */, F5F12A7A1A2F78490015982C /* APCAxisView.m in Sources */, 6CD8B6751AC5011D0061E6D6 /* APCTaskReminder.m in Sources */, F5B947EC1A73272C0034C522 /* APCDebugWindow.m in Sources */, @@ -4026,6 +4032,7 @@ 366345AA1B1F8666003CC0EF /* APCTopLevelScheduleEnumerator.m in Sources */, F5B946301A7309A20034C522 /* ZZDataChannelOutput.m in Sources */, F5F12AA51A2F78490015982C /* APCSignInViewController.m in Sources */, + 5B2CD8611B66184B00DF8E08 /* APCBarGraphView.m in Sources */, F5F12ADB1A2F78490015982C /* APCPickerTableViewCell.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCBarGraphView.h b/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCBarGraphView.h new file mode 100644 index 00000000..e7747ca2 --- /dev/null +++ b/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCBarGraphView.h @@ -0,0 +1,86 @@ +// +// APCBarGraphView.h +// APCAppCore +// +// Copyright (c) 2015, Apple Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder(s) nor the names of any contributors +// may be used to endorse or promote products derived from this software without +// specific prior written permission. No license is granted to the trademarks of +// the copyright holders even if such marks are included in this software. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#import "APCBaseGraphView.h" + +@protocol APCBarGraphViewDataSource; +@protocol APCBarGraphViewDelegate; + +@class APCStackedDataPoint; + +@interface APCBarGraphView : APCBaseGraphView + +@property (nonatomic, weak) IBOutlet id datasource; + +@property (nonatomic, strong) UIView *plotsView; +@property (nonatomic) CGFloat barWidth; + +@end + +@protocol APCBarGraphViewDataSource + +@required + +- (NSInteger)barGraph:(APCBarGraphView *)graphView numberOfPointsInPlot:(NSInteger)plotIndex; + +- (APCStackedDataPoint *)barGraph:(APCBarGraphView *)graphView plot:(NSInteger)plotIndex valueForPointAtIndex:(NSInteger)pointIndex; + +@optional + +- (NSInteger)numberOfPlotsInBarGraph:(APCBarGraphView *)graphView; + +- (NSInteger)numberOfDivisionsInXAxisForBarGraph:(APCBarGraphView *)graphView; + +- (CGFloat)maximumValueForBarGraph:(APCBarGraphView *)graphView; + +- (CGFloat)minimumValueForBarGraph:(APCBarGraphView *)graphView; + +- (NSString *)barGraph:(APCBarGraphView *)graphView titleForXAxisAtIndex:(NSInteger)pointIndex; + +- (UIColor *)barGraph:(APCBarGraphView *)graphView fillColorForStackedLayerAtIndex:(NSInteger)layerIndex; + +@end + +/*********************************/ +/* Stacked Data Point Interface */ +/*******************************/ + +@interface APCStackedDataPoint : NSObject + +@property (nonatomic, strong, readonly) NSArray *stackedValues; + +@property (nonatomic) CGFloat total; + +- (instancetype)initWithStackedValues:(NSArray *)values; + +@end \ No newline at end of file diff --git a/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCBarGraphView.m b/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCBarGraphView.m new file mode 100644 index 00000000..7bf26c1e --- /dev/null +++ b/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCBarGraphView.m @@ -0,0 +1,993 @@ +// +// APCBarGraphView.m +// APCAppCore +// +// Copyright (c) 2015, Apple Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder(s) nor the names of any contributors +// may be used to endorse or promote products derived from this software without +// specific prior written permission. No license is granted to the trademarks of +// the copyright holders even if such marks are included in this software. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#import "APCBarGraphView.h" +#import "APCCircleView.h" +#import "APCAxisView.h" + +static CGFloat const kYAxisPaddingFactor = 0.15f; +static CGFloat const kAPCGraphLeftPadding = 10.f; +static CGFloat const kAxisMarkingRulerLength = 8.0f; + +static CGFloat const kSnappingClosenessFactor = 0.3f; + + +@interface APCShapeLayer : CAShapeLayer + ++ (instancetype)layer; + +@property (nonatomic, strong) UIBezierPath *finalPath; + +@end + +@implementation APCShapeLayer + ++ (instancetype)layer +{ + return [super layer]; +} + +@end + +@interface APCBarGraphView () + +@property (nonatomic, strong) NSMutableArray *dataPoints;//actual data +@property (nonatomic, strong) NSMutableArray *xAxisPoints; +@property (nonatomic, strong) NSMutableArray *yAxisPoints;//normalised for this view + +@property (nonatomic, strong) APCAxisView *xAxisView; +@property (nonatomic, strong) UIView *yAxisView; +@property (nonatomic, strong) UILabel *emptyLabel; +@property (nonatomic) BOOL hasDataPoint; + +@property (nonatomic, strong) UIView *scrubberLine; +@property (nonatomic, strong) UILabel *scrubberLabel; +@property (nonatomic, strong) UIView *scrubberThumbView; + +@property (nonatomic, readwrite) CGFloat minimumValue; +@property (nonatomic, readwrite) CGFloat maximumValue; + +@property (nonatomic, strong) NSMutableArray *xAxisTitles; +@property (nonatomic) NSInteger numberOfXAxisTitles; + +@property (nonatomic, strong) NSMutableArray *referenceLines; +@property (nonatomic, strong) NSMutableArray *barLayers; + +@property (nonatomic) BOOL shouldAnimate; + +@end + +@implementation APCBarGraphView + +@synthesize tintColor = _tintColor; +@synthesize maximumValue = _maximumValue; +@synthesize minimumValue = _minimumValue; + +/********************************/ +#pragma mark - Init +/********************************/ + +- (instancetype)initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + } + return self; +} + +- (id)initWithCoder:(NSCoder *)aDecoder +{ + if (self = [super initWithCoder:aDecoder]) { + } + return self; +} + +- (void)sharedInit +{ + [super sharedInit]; + + _dataPoints = [NSMutableArray new]; + + _xAxisPoints = [NSMutableArray new]; + _yAxisPoints = [NSMutableArray new]; + + _xAxisTitles = [NSMutableArray new]; + + _referenceLines = [NSMutableArray new]; + _barLayers = [NSMutableArray new]; + + _tintColor = [UIColor colorWithRed:244/255.f green:190/255.f blue:74/255.f alpha:1.f]; + + _hasDataPoint = NO; + _shouldAnimate = YES; + + _barWidth = 20.f; + + [self setupViews]; +} + +- (void)setupViews +{ + /* ----------------- */ + /* Basic Views */ + /* ----------------- */ + + _plotsView = [UIView new]; + _plotsView.backgroundColor = [UIColor clearColor]; + [self addSubview:_plotsView]; + + /* ----------------- */ + /* Scrubber Views */ + /* ----------------- */ + _scrubberLine = [UIView new]; + _scrubberLine.backgroundColor = self.scrubberLineColor; + _scrubberLine.alpha = 0; + [self addSubview:_scrubberLine]; + + _scrubberLabel = [UILabel new]; + _scrubberLabel.font = [UIFont fontWithName:@"Helvetica-Light" size:12.0f]; + _scrubberLabel.alpha = 0; + _scrubberLabel.layer.cornerRadius = 2.0f; + _scrubberLabel.layer.borderColor = [UIColor darkGrayColor].CGColor; + _scrubberLabel.layer.borderWidth = 1.0f; + _scrubberLabel.textAlignment = NSTextAlignmentCenter; + _scrubberLabel.frame = CGRectMake(2, 0, 100, 20); + _scrubberLabel.backgroundColor = [UIColor colorWithWhite:0.98 alpha:0.8]; + [self addSubview:_scrubberLabel]; + + _scrubberThumbView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [self scrubberThumbSize].width, [self scrubberThumbSize].height)]; + _scrubberThumbView.layer.borderWidth = 1.0; + _scrubberThumbView.backgroundColor = self.scrubberThumbColor; + _scrubberThumbView.layer.borderColor = [UIColor darkGrayColor].CGColor; + _scrubberThumbView.alpha = 0; + [self addSubview:_scrubberThumbView]; + + self.panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)]; + self.panGestureRecognizer.delaysTouchesBegan = YES; + [self addGestureRecognizer:self.panGestureRecognizer]; +} + +- (void)setDefaults +{ + self.minimumValue = MAXFLOAT; + self.maximumValue = -MAXFLOAT; +} + +- (NSString *)formatNumber:(NSNumber *)value +{ + NSString *formattedNumber = nil; + NSString *suffix = @"k"; + NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; + numberFormatter.numberStyle = NSNumberFormatterDecimalStyle; + + if ([value doubleValue] < 1000) { + [numberFormatter setMaximumFractionDigits:0]; + formattedNumber = [numberFormatter stringFromNumber:value]; + } else { + NSNumber *divdedValue = @([value doubleValue]/1000); + [numberFormatter setMaximumFractionDigits:2]; + formattedNumber = [NSString stringWithFormat:@"%@%@", [numberFormatter stringFromNumber:divdedValue], suffix]; + } + + return formattedNumber; +} + +/********************************/ +#pragma mark - Appearance +/********************************/ + +- (void)updateScrubberLabel +{ + if (self.isLandscapeMode) { + self.scrubberLabel.font = [UIFont fontWithName:self.scrubberLabel.font.familyName size:14.0f]; + } else { + self.scrubberLabel.font = [UIFont fontWithName:self.scrubberLabel.font.familyName size:12.0f]; + } +} + +- (CGSize)scrubberThumbSize +{ + CGSize thumbSize; + + if (self.isLandscapeMode) { + thumbSize = CGSizeMake(15, 15); + } else{ + thumbSize = CGSizeMake(10, 10); + } + + return thumbSize; +} + +/********************************/ +#pragma mark - View Layout +/********************************/ + +- (void)layoutSubviews +{ + [super layoutSubviews]; + + CGFloat yAxisPadding = CGRectGetWidth(self.frame)*kYAxisPaddingFactor; + + //Basic Views + + self.plotsView.frame = CGRectMake(kAPCGraphLeftPadding, kAPCGraphTopPadding, CGRectGetWidth(self.frame) - yAxisPadding - kAPCGraphLeftPadding, CGRectGetHeight(self.frame) - kXAxisHeight - kAPCGraphTopPadding); + + if (self.emptyLabel) { + self.emptyLabel.frame = CGRectMake(kAPCGraphLeftPadding, kAPCGraphTopPadding, CGRectGetWidth(self.frame) - kAPCGraphLeftPadding, CGRectGetHeight(self.frame) - kXAxisHeight - kAPCGraphTopPadding); + } + + //Scrubber Views + self.scrubberLine.frame = CGRectMake(CGRectGetMinX(self.scrubberLine.frame), kAPCGraphTopPadding, 1, CGRectGetHeight(self.plotsView.frame)); + [self updateScrubberLabel]; + self.scrubberThumbView.frame = CGRectMake(CGRectGetMinX(self.scrubberThumbView.frame), CGRectGetMinY(self.scrubberThumbView.frame), [self scrubberThumbSize].width, [self scrubberThumbSize].height); + self.scrubberThumbView.layer.cornerRadius = self.scrubberThumbView.bounds.size.height/2; + + [self.xAxisView layoutSubviews]; + +} + +- (void)refreshGraph +{ + //Clear subviews and sublayers + [self.plotsView.layer.sublayers makeObjectsPerformSelector:@selector(removeAllAnimations)]; + [self.plotsView.layer.sublayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)]; + + [self drawXAxis]; + [self drawYAxis]; + + if (self.showsHorizontalReferenceLines) { + [self drawhorizontalReferenceLines]; + } + + if (self.showsVerticalReferenceLines) { + [self drawVerticalReferenceLines]; + } + + [self calculateXAxisPoints]; + + [self.barLayers removeAllObjects]; + + for (int i=0; i<[self numberOfPlots]; i++) { + if ([self numberOfPointsInPlot:i] <= 1) { + return; + } else { + [self drawGraphForPlotIndex:i]; + } + } + + if (!self.hasDataPoint) { + [self setupEmptyView]; + } else { + if (self.emptyLabel) { + [self.emptyLabel removeFromSuperview]; + } + } + + [self animateLayersSequentially]; + +} + +- (void)setupEmptyView +{ + if (!_emptyLabel) { + + _emptyLabel = [[UILabel alloc] initWithFrame:CGRectMake(kAPCGraphLeftPadding, kAPCGraphTopPadding, CGRectGetWidth(self.frame) - kAPCGraphLeftPadding, CGRectGetHeight(self.frame) - kXAxisHeight - kAPCGraphTopPadding)]; + _emptyLabel.text = self.emptyText; + _emptyLabel.textAlignment = NSTextAlignmentCenter; + _emptyLabel.font = [UIFont fontWithName:@"Helvetica" size:25]; + _emptyLabel.textColor = [UIColor lightGrayColor]; + } + + [self addSubview:_emptyLabel]; +} + +/********************************/ +#pragma mark - Data +/********************************/ + +- (NSInteger)numberOfPlots +{ + NSInteger numberOfPlots = 1; + + if ([self.datasource respondsToSelector:@selector(numberOfPlotsInBarGraph:)]) { + numberOfPlots = [self.datasource numberOfPlotsInBarGraph:self]; + } + + return numberOfPlots; +} + +- (NSInteger)numberOfPointsInPlot:(NSInteger)plotIndex +{ + NSInteger numberOfPoints = 0; + + if ([self.datasource respondsToSelector:@selector(barGraph:numberOfPointsInPlot:)]) { + numberOfPoints = [self.datasource barGraph:self numberOfPointsInPlot:plotIndex]; + + } + + return numberOfPoints; +} + +- (NSInteger)numberOfXAxisTitles +{ + _numberOfXAxisTitles = 0; + + if ([self.datasource respondsToSelector:@selector(numberOfDivisionsInXAxisForBarGraph:)]) { + _numberOfXAxisTitles = [self.datasource numberOfDivisionsInXAxisForBarGraph:self]; + } else { + _numberOfXAxisTitles = [self numberOfPointsInPlot:0]; + } + + return _numberOfXAxisTitles; +} + +- (UIColor *)colorForStackedLayerIndex:(NSInteger)index +{ + UIColor *fillColor = self.tintColor; + + if ([self.datasource respondsToSelector:@selector(barGraph:fillColorForStackedLayerAtIndex:)]) { + fillColor = [self.datasource barGraph:self fillColorForStackedLayerAtIndex:index]; + } else { + fillColor = [self.tintColor colorWithAlphaComponent:(1 - 0.25*index)]; + } + + return fillColor; +} + +- (void)calculateXAxisPoints +{ + [self.xAxisPoints removeAllObjects]; + + for (int i=0 ; i<[self.dataPoints count]; i++) { + + CGFloat positionOnXAxis = ((CGRectGetWidth(self.plotsView.frame) / (self.yAxisPoints.count - 1)) * i); + positionOnXAxis = round(positionOnXAxis); + [self.xAxisPoints addObject:@(positionOnXAxis)]; + } +} + +- (void)prepareDataForPlotIndex:(NSInteger)plotIndex +{ + [self.dataPoints removeAllObjects]; + [self.yAxisPoints removeAllObjects]; + self.hasDataPoint = NO; + for (int i = 0; i<[self numberOfPointsInPlot:plotIndex]; i++) { + + if ([self.datasource respondsToSelector:@selector(barGraph:plot:valueForPointAtIndex:)]) { + APCStackedDataPoint *values = [self.datasource barGraph:self plot:plotIndex valueForPointAtIndex:i]; + + if (values) { + [self.dataPoints addObject:values]; + self.hasDataPoint = YES; + } + } + } + + [self.yAxisPoints addObjectsFromArray:[self normalizeCanvasPoints:self.dataPoints forRect:self.plotsView.frame.size]]; +} + +/********************************/ +#pragma mark - Draw +/********************************/ + +- (void)drawXAxis +{ + //Add Title Labels + [self.xAxisTitles removeAllObjects]; + + for (int i=0; i 0) { + [self drawBarsForPlotIndex:plotIndex]; + } +} + +- (void)drawBarsForPlotIndex:(NSInteger)plotIndex +{ + CGFloat positionOnXAxis = CGFLOAT_MAX; + APCStackedDataPoint *positionOnYAxisValues = nil; + + CGFloat barWidth = MIN(self.barWidth, [self maximumAllowedBarWidth]); + + for (NSUInteger i=0; i 0) { + + positionOnXAxis = [self.xAxisPoints[i] floatValue]; + positionOnXAxis += [self offsetForPlotIndex:plotIndex]; + + positionOnYAxisValues = ((APCStackedDataPoint *)self.yAxisPoints[i]); + + CGFloat prevYValue = CGRectGetHeight(self.plotsView.bounds); + + for (int j=0; j<[positionOnYAxisValues.stackedValues count]; j++) { + + CGFloat yPositionValue = [positionOnYAxisValues.stackedValues[j] floatValue]; + + CGFloat barHeight = fabs(yPositionValue - prevYValue); + + UIBezierPath *barPath = [UIBezierPath bezierPathWithRect:CGRectMake(positionOnXAxis - barWidth/2, prevYValue - barHeight, barWidth, barHeight)]; + + APCShapeLayer *barLayer = [APCShapeLayer layer]; + + if (self.shouldAnimate) { + UIBezierPath *zeroHeightBarPath = [UIBezierPath bezierPathWithRect:CGRectMake(positionOnXAxis - barWidth/2, CGRectGetHeight(self.plotsView.bounds), barWidth, 0)]; + barLayer.path = zeroHeightBarPath.CGPath; + barLayer.finalPath = barPath; + + [self.barLayers addObject:barLayer]; + + } else { + barLayer.path = barPath.CGPath; + } + + barLayer.fillColor = [self colorForStackedLayerIndex:j].CGColor; + [self.plotsView.layer addSublayer:barLayer]; + + prevYValue = yPositionValue; + } + } + } +} + +- (CGFloat)offsetForPlotIndex:(NSInteger)plotIndex +{ + CGFloat barWidth = MIN(self.barWidth, [self maximumAllowedBarWidth]); + + NSInteger numberOfPlots = [self numberOfPlots]; + + CGFloat offset = 0; + + if (numberOfPlots%2 == 0) { + //Even + offset = (plotIndex - numberOfPlots/2 + 0.5) * barWidth; + } else { + //Odd + offset = (plotIndex - numberOfPlots/2) * barWidth; + } + + return offset; +} + +- (CGFloat)maximumAllowedBarWidth +{ + CGFloat maxWidth = CGRectGetWidth(self.plotsView.bounds)/[self.dataPoints count]; + maxWidth = maxWidth/[self numberOfPlots]; + + return maxWidth; +} + +#pragma mark - Graph Calculations + +- (NSInteger)numberOfValidValues +{ + NSInteger count = 0; + + for (APCStackedDataPoint *dataVal in self.dataPoints) { + if (dataVal.stackedValues) { + count ++; + } + } + return count; +} + +- (void)calculateMinAndMaxPoints +{ + [self setDefaults]; + + //Min + if ([self.datasource respondsToSelector:@selector(minimumValueForBarGraph:)]) { + self.minimumValue = [self.datasource minimumValueForBarGraph:self]; + } else { + + if (self.dataPoints.count) { + self.minimumValue = ((APCStackedDataPoint *)self.dataPoints[0]).total; + + for (NSUInteger i=1; i self.maximumValue)) || (self.maximumValue == NSNotFound)) { + self.maximumValue = num; + } + } + } + } +} + +- (NSArray *)normalizeCanvasPoints:(NSArray *) __unused dataPoints forRect:(CGSize)canvasSize +{ + [self calculateMinAndMaxPoints]; + + NSMutableArray *normalizedPoints = [NSMutableArray new]; + + for (NSUInteger i=0; i 0) { + if (((APCStackedDataPoint *)self.dataPoints[validPosition]).stackedValues) { + break; + } + validPosition --; + } + + return validPosition; +} + +- (CGFloat)snappedXPosition:(CGFloat)xPosition +{ + CGFloat widthBetweenPoints = CGRectGetWidth(self.plotsView.frame)/self.xAxisPoints.count; + + NSUInteger positionIndex; + for (positionIndex = 0; positionIndex 0) { + CGFloat alpha = hidden ? 0 : 1; + + if (animated) { + [UIView animateWithDuration:0.2 animations:^{ + self.scrubberThumbView.alpha = alpha; + self.scrubberLine.alpha = alpha; + self.scrubberLabel.alpha = alpha; + }]; + } else { + self.scrubberThumbView.alpha = alpha; + self.scrubberLine.alpha = alpha; + self.scrubberLabel.alpha = alpha; + } + } +} + +/********************************/ +#pragma mark - Touch +/********************************/ + +- (void)handlePanGesture:(UIPanGestureRecognizer *)gestureRecognizer +{ + if ((self.dataPoints.count > 0) && [self numberOfValidValues] > 0) { + CGPoint location = [gestureRecognizer locationInView:self.plotsView]; + + location = CGPointMake(location.x, location.y); + + CGFloat maxX = round(CGRectGetWidth(self.plotsView.bounds)); + CGFloat minX = 0; + + CGFloat normalizedX = MAX(MIN(location.x, maxX), minX); + location = CGPointMake(normalizedX, location.y); + + //--------------- + + CGFloat snappedXPosition = [self snappedXPosition:location.x]; + [self scrubberViewForXPosition:snappedXPosition]; + + //--------------- + + if ([self.delegate respondsToSelector:@selector(graphView:touchesMovedToXPosition:)]) { + [self.delegate graphView:self touchesMovedToXPosition:snappedXPosition]; + } + + if (gestureRecognizer.state == UIGestureRecognizerStateBegan) { + [self setScrubberViewsHidden:NO animated:YES]; + if ([self.delegate respondsToSelector:@selector(graphViewTouchesBegan:)]) { + [self.delegate graphViewTouchesBegan:self]; + } + } else if (gestureRecognizer.state == UIGestureRecognizerStateEnded){ + [self setScrubberViewsHidden:YES animated:YES]; + if ([self.delegate respondsToSelector:@selector(graphViewTouchesEnded:)]) { + [self.delegate graphViewTouchesEnded:self]; + } + } + } +} + +- (void)scrubberViewForXPosition:(CGFloat)xPosition +{ + + self.scrubberLine.center = CGPointMake(xPosition + kAPCGraphLeftPadding, self.scrubberLine.center.y); + + CGFloat scrubbingVal = [self valueForCanvasXPosition:(xPosition)]; + + self.scrubberLabel.text = [NSString stringWithFormat:@"%.0f", scrubbingVal]; + + CGSize textSize = [self.scrubberLabel.text boundingRectWithSize:CGSizeMake(320, CGRectGetHeight(self.scrubberLabel.bounds)) options:(NSStringDrawingUsesFontLeading|NSStringDrawingUsesLineFragmentOrigin) attributes:@{NSFontAttributeName:self.scrubberLabel.font} context:nil].size; + + [self.scrubberLabel setFrame:CGRectMake(CGRectGetMaxX(self.scrubberLine.frame) + 6, CGRectGetMinY(self.scrubberLine.frame), textSize.width + 8, CGRectGetHeight(self.scrubberLabel.frame))]; + + //--------------- + + CGFloat scrubberYPos = [self canvasYPointForXPosition:xPosition]; + + [self.scrubberThumbView setCenter:CGPointMake(xPosition + kAPCGraphLeftPadding, scrubberYPos + kAPCGraphTopPadding)]; + + if (scrubbingVal >= self.minimumValue && scrubbingVal <= self.maximumValue) { + self.scrubberLabel.alpha = 1; + self.scrubberThumbView.alpha = 1; + } else { + self.scrubberLabel.alpha = 0; + self.scrubberThumbView.alpha = 0; + } +} + +/********************************/ +#pragma mark - Public Methods +/********************************/ + +- (void)scrubReferenceLineForXPosition:(CGFloat)xPosition +{ + if (self.dataPoints.count > 1) { + [self scrubberViewForXPosition:xPosition]; + } +} +@end + + +/**************************************/ +/* Stacked Data Point Implementation */ +/************************************/ + +@implementation APCStackedDataPoint + +- (instancetype)initWithStackedValues:(NSArray *)values +{ + self = [super init]; + if (self) { + _total = 0; + _stackedValues = values; + } + return self; +} + +- (CGFloat)total +{ + CGFloat sum = 0; + + if (self.stackedValues) { + for (NSNumber *value in self.stackedValues) { + sum += [value floatValue]; + } + } + + return sum; +} + +@end diff --git a/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCBaseGraphView.h b/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCBaseGraphView.h index ddd41b73..9bb34f10 100644 --- a/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCBaseGraphView.h +++ b/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCBaseGraphView.h @@ -39,8 +39,9 @@ */ FOUNDATION_EXPORT CGFloat const kAPCFadeAnimationDuration; -FOUNDATION_EXPORT CGFloat const kAPCGrowAnimationDuration; -FOUNDATION_EXPORT CGFloat const kAPCPopAnimationDuration; +FOUNDATION_EXPORT CGFloat const kAPCStrokeAnimationDuration; +FOUNDATION_EXPORT CGFloat const kAPCScaleAnimationDuration; +FOUNDATION_EXPORT CGFloat const kAPCPathAnimationDuration; @protocol APCBaseGraphViewDelegate; @@ -105,7 +106,7 @@ FOUNDATION_EXPORT CGFloat const kAPCPopAnimationDuration; - (void)refreshGraph; -- (void)animateLayer:(CAShapeLayer *)shapeLayer withAnimationType:(APCGraphAnimationType)animationType toValue:(CGFloat)toValue startDelay:(CGFloat)delay; +- (void)animateLayer:(CAShapeLayer *)shapeLayer withAnimationType:(APCGraphAnimationType)animationType toValue:(id)toValue startDelay:(CGFloat)delay; @end diff --git a/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCBaseGraphView.m b/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCBaseGraphView.m index 5cb82c25..ebbff61e 100644 --- a/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCBaseGraphView.m +++ b/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCBaseGraphView.m @@ -34,12 +34,15 @@ #import "APCBaseGraphView.h" #import "UIColor+APCAppearance.h" -static NSString * const kFadeAnimationKey = @"LayerFadeAnimation"; -static NSString * const kGrowAnimationKey = @"LayerGrowAnimation"; +static NSString * const kAPCFadeAnimationKey = @"APCFadeAnimationKey"; +static NSString * const kAPCStrokeAnimationKey = @"APCStrokeAnimationKey"; +static NSString * const kAPCScaleAnimationKey = @"APCScaleAnimationKey"; +static NSString * const kAPCPathAnimationKey = @"APCPathAnimationKey"; -CGFloat const kAPCFadeAnimationDuration = 0.2; -CGFloat const kAPCGrowAnimationDuration = 0.1; -CGFloat const kAPCPopAnimationDuration = 0.3; +CGFloat const kAPCFadeAnimationDuration = 0.2; +CGFloat const kAPCStrokeAnimationDuration = 0.1; +CGFloat const kAPCScaleAnimationDuration = 0.3; +CGFloat const kAPCPathAnimationDuration = 0.7; @implementation APCBaseGraphView @@ -127,60 +130,50 @@ - (void)setDisableScrubbing:(BOOL)disableScrubbing #pragma mark - Animations -- (void)animateLayer:(CAShapeLayer *)shapeLayer withAnimationType:(APCGraphAnimationType)animationType -{ - [self animateLayer:shapeLayer withAnimationType:animationType toValue:1.0]; -} - -- (void)animateLayer:(CAShapeLayer *)shapeLayer withAnimationType:(APCGraphAnimationType)animationType toValue:(CGFloat)toValue -{ - [self animateLayer:shapeLayer withAnimationType:animationType toValue:toValue startDelay:0.0]; -} - -- (void)animateLayer:(CAShapeLayer *)shapeLayer withAnimationType:(APCGraphAnimationType)animationType startDelay:(CGFloat)delay -{ - [self animateLayer:shapeLayer withAnimationType:animationType toValue:1.0 startDelay:delay]; -} - -- (void)animateLayer:(CAShapeLayer *)shapeLayer withAnimationType:(APCGraphAnimationType)animationType toValue:(CGFloat)toValue startDelay:(CGFloat)delay +- (void)animateLayer:(CAShapeLayer *)shapeLayer withAnimationType:(APCGraphAnimationType)animationType toValue:(id)toValue startDelay:(CGFloat)delay { + NSString *animationKeyPath; + NSString *animationKeyName; + CFTimeInterval animationDuration = 0; + if (animationType == kAPCGraphAnimationTypeFade) { - CABasicAnimation *fadeAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; - fadeAnimation.beginTime = CACurrentMediaTime() + delay; - fadeAnimation.fromValue = @0; - fadeAnimation.toValue = @(toValue); - fadeAnimation.duration = kAPCFadeAnimationDuration; - fadeAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; - fadeAnimation.fillMode = kCAFillModeForwards; - fadeAnimation.removedOnCompletion = NO; - [shapeLayer addAnimation:fadeAnimation forKey:kFadeAnimationKey]; + animationKeyPath = @"opacity"; + animationKeyName = kAPCFadeAnimationKey; + animationDuration = kAPCFadeAnimationDuration; - } else if (animationType == kAPCGraphAnimationTypeGrow) { + } else if (animationType == kAPCGraphAnimationTypeStrokeStart) { - CABasicAnimation *growAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; - growAnimation.beginTime = CACurrentMediaTime() + delay; - growAnimation.fromValue = @0; - growAnimation.toValue = @(toValue); - growAnimation.duration = kAPCGrowAnimationDuration; - growAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; - growAnimation.fillMode = kCAFillModeForwards; - growAnimation.removedOnCompletion = NO; - [shapeLayer addAnimation:growAnimation forKey:kGrowAnimationKey]; + animationKeyPath = @"strokeStart"; + animationKeyName = kAPCStrokeAnimationKey; + animationDuration = kAPCStrokeAnimationDuration; - } else if (animationType == kAPCGraphAnimationTypePop) { + } else if (animationType == kAPCGraphAnimationTypeStrokeEnd) { - CABasicAnimation *popAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"]; - popAnimation.beginTime = CACurrentMediaTime() + delay; - popAnimation.fromValue = @0; - popAnimation.toValue = @(toValue); - popAnimation.duration = kAPCPopAnimationDuration; - popAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; - popAnimation.fillMode = kCAFillModeForwards; - popAnimation.removedOnCompletion = NO; - [shapeLayer addAnimation:popAnimation forKey:kGrowAnimationKey]; + animationKeyPath = @"strokeEnd"; + animationKeyName = kAPCStrokeAnimationKey; + animationDuration = kAPCStrokeAnimationDuration; + }else if (animationType == kAPCGraphAnimationTypeScale) { + + animationKeyPath = @"transform.scale"; + animationKeyName = kAPCScaleAnimationKey; + animationDuration = kAPCScaleAnimationDuration; + + } else if (animationType == kAPCGraphAnimationTypePath){ + animationKeyPath = @"path"; + animationKeyName = kAPCPathAnimationKey; + animationDuration = kAPCPathAnimationDuration; } + + CABasicAnimation *growAnimation = [CABasicAnimation animationWithKeyPath:animationKeyPath]; + growAnimation.beginTime = CACurrentMediaTime() + delay; + growAnimation.toValue = toValue; + growAnimation.duration = animationDuration; + growAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; + growAnimation.fillMode = kCAFillModeForwards; + growAnimation.removedOnCompletion = NO; + [shapeLayer addAnimation:growAnimation forKey:animationKeyName]; } @end diff --git a/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCDiscreteGraphView.h b/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCDiscreteGraphView.h index 685626a1..70d0b37d 100644 --- a/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCDiscreteGraphView.h +++ b/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCDiscreteGraphView.h @@ -34,9 +34,6 @@ #import #import "APCBaseGraphView.h" -FOUNDATION_EXPORT NSString * const kAPCDiscreteGraphViewTriggerAnimationsNotification; -FOUNDATION_EXPORT NSString * const kAPCDiscreteGraphViewRefreshNotification; - @protocol APCDiscreteGraphViewDataSource; @protocol APCDiscreteGraphViewDelegate; @class APCRangePoint; @@ -61,7 +58,7 @@ FOUNDATION_EXPORT NSString * const kAPCDiscreteGraphViewRefreshNotification; - (NSInteger)numberOfPlotsInDiscreteGraph:(APCDiscreteGraphView *)graphView; -- (NSInteger)numberOfDivisionsInXAxisForGraph:(APCDiscreteGraphView *)graphView; +- (NSInteger)numberOfDivisionsInXAxisForDiscreteGraph:(APCDiscreteGraphView *)graphView; - (CGFloat)maximumValueForDiscreteGraph:(APCDiscreteGraphView *)graphView; diff --git a/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCDiscreteGraphView.m b/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCDiscreteGraphView.m index 4e604226..4ef2321d 100644 --- a/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCDiscreteGraphView.m +++ b/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCDiscreteGraphView.m @@ -1,43 +1,40 @@ -// -// APCDiscreteGraphView.m -// APCAppCore -// -// Copyright (c) 2015, Apple Inc. All rights reserved. -// +// +// APCDiscreteGraphView.m +// APCAppCore +// +// Copyright (c) 2015, Apple Inc. All rights reserved. +// // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: -// +// // 1. Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation and/or -// other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder(s) nor the names of any contributors -// may be used to endorse or promote products derived from this software without -// specific prior written permission. No license is granted to the trademarks of -// the copyright holders even if such marks are included in this software. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// - +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder(s) nor the names of any contributors +// may be used to endorse or promote products derived from this software without +// specific prior written permission. No license is granted to the trademarks of +// the copyright holders even if such marks are included in this software. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + #import "APCDiscreteGraphView.h" #import "APCCircleView.h" #import "APCAxisView.h" -NSString * const kAPCDiscreteGraphViewTriggerAnimationsNotification = @"APCDiscreteGraphViewTriggerAnimationsNotification"; -NSString * const kAPCDiscreteGraphViewRefreshNotification = @"APCDiscreteGraphViewRefreshNotification"; - static CGFloat const kYAxisPaddingFactor = 0.15f; static CGFloat const kAPCGraphLeftPadding = 10.f; static CGFloat const kAxisMarkingRulerLength = 8.0f; @@ -81,7 +78,9 @@ @implementation APCDiscreteGraphView @synthesize maximumValue = _maximumValue; @synthesize minimumValue = _minimumValue; +/********************************/ #pragma mark - Init +/********************************/ - (instancetype)initWithFrame:(CGRect)frame { @@ -119,9 +118,6 @@ - (void)sharedInit _shouldConnectRanges = YES; [self setupViews]; - - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(animateLayersSequentially) name:kAPCDiscreteGraphViewTriggerAnimationsNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshGraph) name:kAPCDiscreteGraphViewRefreshNotification object:nil]; } - (void)setupViews @@ -190,7 +186,9 @@ - (NSString *)formatNumber:(NSNumber *)value return formattedNumber; } +/********************************/ #pragma mark - Appearance +/********************************/ - (void)updateScrubberLabel { @@ -214,8 +212,9 @@ - (CGSize)scrubberThumbSize return thumbSize; } - +/********************************/ #pragma mark - View Layout +/********************************/ - (void)layoutSubviews { @@ -297,7 +296,9 @@ - (void)setupEmptyView [self addSubview:_emptyLabel]; } +/********************************/ #pragma mark - Data +/********************************/ - (NSInteger)numberOfPlots { @@ -326,8 +327,8 @@ - (NSInteger)numberOfXAxisTitles { _numberOfXAxisTitles = 0; - if ([self.datasource respondsToSelector:@selector(numberOfDivisionsInXAxisForGraph:)]) { - _numberOfXAxisTitles = [self.datasource numberOfDivisionsInXAxisForGraph:self]; + if ([self.datasource respondsToSelector:@selector(numberOfDivisionsInXAxisForDiscreteGraph:)]) { + _numberOfXAxisTitles = [self.datasource numberOfDivisionsInXAxisForDiscreteGraph:self]; } else { _numberOfXAxisTitles = [self numberOfPointsInPlot:0]; } @@ -339,7 +340,7 @@ - (void)calculateXAxisPoints { [self.xAxisPoints removeAllObjects]; - for (int i=0 ; i<[self numberOfXAxisTitles]; i++) { + for (NSUInteger i=0 ; i<[self.dataPoints count]; i++) { CGFloat positionOnXAxis = ((CGRectGetWidth(self.plotsView.frame) / (self.yAxisPoints.count - 1)) * i); positionOnXAxis = round(positionOnXAxis); @@ -370,7 +371,9 @@ - (void)prepareDataForPlotIndex:(NSInteger)plotIndex [self.yAxisPoints addObjectsFromArray:[self normalizeCanvasPoints:self.dataPoints forRect:self.plotsView.frame.size]]; } +/********************************/ #pragma mark - Draw +/********************************/ - (void)drawXAxis { @@ -605,10 +608,10 @@ - (void)drawLinesForPlotIndex:(NSInteger)plotIndex if (!dataPointVal.isEmpty && !dataPointVal.isRangeZero) { UIBezierPath *plotLinePath = [UIBezierPath bezierPath]; - + positionOnXAxis = [self.xAxisPoints[i] floatValue]; positionOnXAxis += [self offsetForPlotIndex:plotIndex]; - + positionOnYAxis = ((APCRangePoint *)self.yAxisPoints[i]); [plotLinePath moveToPoint:CGPointMake(positionOnXAxis, positionOnYAxis.minimumValue)]; @@ -757,7 +760,7 @@ - (CGFloat)valueForCanvasXPosition:(CGFloat)xPosition value = ((APCRangePoint *)self.dataPoints[positionIndex]).maximumValue; } - + return value; } @@ -836,7 +839,9 @@ - (CGFloat)snappedXPosition:(CGFloat)xPosition return xPosition; } +/********************************/ #pragma mark - Animations +/********************************/ - (void)animateLayersSequentially { @@ -844,14 +849,14 @@ - (void)animateLayersSequentially for (NSUInteger i=0; i #import "APCBaseGraphView.h" -FOUNDATION_EXPORT NSString * const kAPCLineGraphViewTriggerAnimationsNotification; -FOUNDATION_EXPORT NSString * const kAPCLineGraphViewRefreshNotification; - @protocol APCLineGraphViewDataSource; @protocol APCLineGraphViewDelegate; @@ -62,7 +59,7 @@ FOUNDATION_EXPORT NSString * const kAPCLineGraphViewRefreshNotification; - (NSInteger)numberOfPlotsInLineGraph:(APCLineGraphView *)graphView; -- (NSInteger)numberOfDivisionsInXAxisForGraph:(APCLineGraphView *)graphView; +- (NSInteger)numberOfDivisionsInXAxisForLineGraph:(APCLineGraphView *)graphView; - (CGFloat)maximumValueForLineGraph:(APCLineGraphView *)graphView; diff --git a/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCLineGraphView.m b/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCLineGraphView.m index 3e177bc2..0042ecf7 100644 --- a/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCLineGraphView.m +++ b/APCAppCore/APCAppCore/UI/Components/GraphCharts/APCLineGraphView.m @@ -36,9 +36,6 @@ #import "APCAxisView.h" #import "APCCubicCurveAlgorithm.h" -NSString * const kAPCLineGraphViewTriggerAnimationsNotification = @"APCLineGraphViewTriggerAnimationsNotification"; -NSString * const kAPCLineGraphViewRefreshNotification = @"APCLineGraphViewRefreshNotification"; - static CGFloat const kYAxisPaddingFactor = 0.15f; static CGFloat const kAPCGraphLeftPadding = 10.f; static CGFloat const kAxisMarkingRulerLength = 8.0f; @@ -83,7 +80,10 @@ @implementation APCLineGraphView @synthesize maximumValue = _maximumValue; @synthesize minimumValue = _minimumValue; + +/********************************/ #pragma mark - Init +/********************************/ - (instancetype)initWithFrame:(CGRect)frame { @@ -124,9 +124,6 @@ - (void)sharedInit [self setupViews]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(animateLayersSequentially) name:kAPCLineGraphViewTriggerAnimationsNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshGraph) name:kAPCLineGraphViewRefreshNotification object:nil]; - _smoothCurveGenerator = [APCCubicCurveAlgorithm new]; } @@ -196,7 +193,9 @@ - (NSString *)formatNumber:(NSNumber *)value return formattedNumber; } +/********************************/ #pragma mark - Appearance +/********************************/ - (void)updateScrubberLabel { @@ -220,8 +219,9 @@ - (CGSize)scrubberThumbSize return thumbSize; } - +/********************************/ #pragma mark - View Layout +/********************************/ - (void)layoutSubviews { @@ -304,7 +304,9 @@ - (void)setupEmptyView [self addSubview:_emptyLabel]; } +/********************************/ #pragma mark - Data +/********************************/ - (NSInteger)numberOfPlots { @@ -333,8 +335,8 @@ - (NSInteger)numberOfXAxisTitles { _numberOfXAxisTitles = 0; - if ([self.datasource respondsToSelector:@selector(numberOfDivisionsInXAxisForGraph:)]) { - _numberOfXAxisTitles = [self.datasource numberOfDivisionsInXAxisForGraph:self]; + if ([self.datasource respondsToSelector:@selector(numberOfDivisionsInXAxisForLineGraph:)]) { + _numberOfXAxisTitles = [self.datasource numberOfDivisionsInXAxisForLineGraph:self]; } else { _numberOfXAxisTitles = [self numberOfPointsInPlot:0]; } @@ -378,7 +380,9 @@ - (void)prepareDataForPlotIndex:(NSInteger)plotIndex [self.yAxisPoints addObjectsFromArray:[self normalizeCanvasPoints:self.dataPoints forRect:self.plotsView.frame.size]]; } +/********************************/ #pragma mark - Draw +/********************************/ - (void)drawXAxis { @@ -687,7 +691,9 @@ - (void)drawLinesForPlotIndex:(NSInteger)plotIndex } } +/********************************/ #pragma mark - Graph Calculations +/********************************/ - (NSInteger)numberOfValidValues { @@ -895,7 +901,9 @@ - (CGFloat)snappedXPosition:(CGFloat)xPosition return xPosition; } +/********************************/ #pragma mark - Animations +/********************************/ - (void)animateLayersSequentially { @@ -903,20 +911,20 @@ - (void)animateLayersSequentially for (NSUInteger i=0; i