├── MMPGraphView ├── MMPGraph │ ├── Resources │ │ └── MMPMedia.xcassets │ │ │ ├── Contents.json │ │ │ ├── arrows.imageset │ │ │ ├── arrows.png │ │ │ └── Contents.json │ │ │ └── MMPBackIcon.imageset │ │ │ ├── backIcon.png │ │ │ ├── backIcon@2x.png │ │ │ ├── backIcon@3x.png │ │ │ └── Contents.json │ ├── Data │ │ ├── MMPGraphDelegate.swift │ │ ├── MMPGraphDataPoint.swift │ │ └── MMPGraphDataSet.swift │ ├── Extensions │ │ └── MMPGraphView+Visuals.swift │ ├── MMPGraphTableViewCell.swift │ ├── MMPGraphViewController.swift │ ├── MMPGraphTableViewCell.xib │ └── MMPGraphView.swift ├── MMPTestData.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── AppDelegate.swift └── ViewController.swift ├── .gitignore ├── MMPGraphView.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── JLoewy.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── xcuserdata │ └── jloewy.xcuserdatad │ │ ├── xcschemes │ │ ├── xcschememanagement.plist │ │ └── MMPGraphView.xcscheme │ │ └── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist └── project.pbxproj └── README.md /MMPGraphView/MMPGraph/Resources/MMPMedia.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.xcuserstate 2 | 3 | *.xcuserstate 4 | 5 | *.xcbkptlist 6 | 7 | *.xcbkptlist 8 | 9 | *.xcuserstate 10 | 11 | *.xcuserstate 12 | 13 | *.xcbkptlist 14 | -------------------------------------------------------------------------------- /MMPGraphView/MMPGraph/Resources/MMPMedia.xcassets/arrows.imageset/arrows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JLoewy/MMPGraphView/HEAD/MMPGraphView/MMPGraph/Resources/MMPMedia.xcassets/arrows.imageset/arrows.png -------------------------------------------------------------------------------- /MMPGraphView/MMPGraph/Resources/MMPMedia.xcassets/MMPBackIcon.imageset/backIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JLoewy/MMPGraphView/HEAD/MMPGraphView/MMPGraph/Resources/MMPMedia.xcassets/MMPBackIcon.imageset/backIcon.png -------------------------------------------------------------------------------- /MMPGraphView/MMPGraph/Resources/MMPMedia.xcassets/MMPBackIcon.imageset/backIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JLoewy/MMPGraphView/HEAD/MMPGraphView/MMPGraph/Resources/MMPMedia.xcassets/MMPBackIcon.imageset/backIcon@2x.png -------------------------------------------------------------------------------- /MMPGraphView/MMPGraph/Resources/MMPMedia.xcassets/MMPBackIcon.imageset/backIcon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JLoewy/MMPGraphView/HEAD/MMPGraphView/MMPGraph/Resources/MMPMedia.xcassets/MMPBackIcon.imageset/backIcon@3x.png -------------------------------------------------------------------------------- /MMPGraphView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MMPGraphView.xcodeproj/project.xcworkspace/xcuserdata/JLoewy.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JLoewy/MMPGraphView/HEAD/MMPGraphView.xcodeproj/project.xcworkspace/xcuserdata/JLoewy.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MMPGraphView 2 | A versatile core graphics graph for iOS in swift 3 | 4 | MMPGraphView allows you to plot one or two sets of data on a single graph drawn with lightweight core graphics. It can hold multiple plots on the same graph and can be initlialized programmatically or through storyboard. 5 | 6 | 7 | -------------------------------------------------------------------------------- /MMPGraphView/MMPGraph/Resources/MMPMedia.xcassets/arrows.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "arrows.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MMPGraphView/MMPGraph/Resources/MMPMedia.xcassets/MMPBackIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "backIcon.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "backIcon@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "backIcon@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /MMPGraphView.xcodeproj/xcuserdata/jloewy.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MMPGraphView.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 037D04A91CBB694700AE0C83 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /MMPGraphView/MMPTestData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MMPTestData.swift 3 | // MMPGraphView 4 | // 5 | // Created by Jason Loewy on 4/10/16. 6 | // Copyright © 2016 My Macros+, LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class MMPTestData: NSObject, MMPGraphDataPoint { 13 | 14 | var title:String 15 | var value:CGFloat 16 | 17 | init(title:String, value:CGFloat) 18 | { 19 | self.title = title 20 | self.value = value 21 | } 22 | 23 | // MARK: - MMPGraphDataPoint Methods 24 | 25 | func mmpGraphTitle() -> String { 26 | return self.title 27 | } 28 | 29 | func mmpGraphValue() -> CGFloat { 30 | return self.value 31 | } 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /MMPGraphView/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /MMPGraphView/MMPGraph/Data/MMPGraphDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MMPGraphDelegate.swift 3 | // MMPGraphView 4 | // 5 | // Created by Jason Loewy on 4/12/16. 6 | // Copyright © 2016 My Macros+, LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | @objc protocol MMPGraphDelegate 13 | { 14 | /** 15 | Resposnible for letting the delegate know that there is a new average value calculated 16 | 17 | - parameter graphView: the currently active graph view 18 | - parameter graphDataAverage: the cgfloat value of the average 19 | - parameter dataSet: the MMPGraphDataSet whos average was just calculated 20 | */ 21 | optional func averageCalculated(graphView:MMPGraphView, graphDataAverage:CGFloat, dataSet:MMPGraphDataSet) 22 | 23 | /** 24 | Responsible for letting the delegate know that the user wants this graph to be presented in full screen mode 25 | 26 | - parameter graphView: The currently active MMPGraphView 27 | */ 28 | optional func showFullScreenGraph(graphView:MMPGraphView) 29 | } -------------------------------------------------------------------------------- /MMPGraphView/MMPGraph/Extensions/MMPGraphView+Visuals.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MMPGraphView+Visuals.swift 3 | // MMPGraphView 4 | // 5 | // Created by Jason Loewy on 4/18/16. 6 | // Copyright © 2016 My Macros+, LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension MMPGraphView { 13 | 14 | static func boldFont()->UIFont { 15 | return MMPGraphView.boldFont(14.0) 16 | } 17 | 18 | static func boldFont(pointSize:CGFloat)->UIFont { 19 | return UIFont.boldSystemFontOfSize(pointSize) 20 | } 21 | 22 | static func regularFont()->UIFont { 23 | return MMPGraphView.regularFont(14.0) 24 | } 25 | 26 | static func regularFont(pointSize:CGFloat)->UIFont { 27 | return UIFont.systemFontOfSize(pointSize) 28 | } 29 | 30 | static func lightFont()->UIFont { 31 | return UIFont.systemFontOfSize(14.0) 32 | } 33 | 34 | /** 35 | arrows example image is made by Vaadin http://www.flaticon.com/authors/vaadin 36 | distributed on http://www.flaticon.com 37 | 38 | - returns: The UIImage to be used in the full screen button 39 | */ 40 | static func fullScreenImage()->UIImage { 41 | return UIImage(named: "arrows")! 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /MMPGraphView/MMPGraph/Data/MMPGraphDataPoint.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MMPGraphDataPoint.swift 3 | // My Workout 4 | // 5 | // Created by Jason Loewy on 4/8/16. 6 | // Copyright © 2016 My Macros LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | @objc protocol MMPGraphDataPoint { 13 | 14 | /** 15 | Responsible for getting the value associated with this data point 16 | 17 | - returns: The CGFloat representation of the data point 18 | */ 19 | func mmpGraphValue()->CGFloat 20 | 21 | /** 22 | Responsible for getting the title associated with this data point 23 | 24 | - returns: The string representation of the data point 25 | */ 26 | func mmpGraphTitle()->String 27 | 28 | } 29 | 30 | /// Generic data object that is designed to feed the GraphDataPoint data source 31 | class MMPGraphData { 32 | 33 | private let title:String 34 | private let value:CGFloat 35 | 36 | init(title:String, value:CGFloat) 37 | { 38 | self.title = title; 39 | self.value = value; 40 | } 41 | 42 | func mmpGraphTitle() -> String { 43 | return self.title 44 | } 45 | 46 | func mmpGraphValue() -> CGFloat { 47 | return self.value 48 | } 49 | } -------------------------------------------------------------------------------- /MMPGraphView.xcodeproj/xcuserdata/jloewy.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 14 | 15 | 16 | 18 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /MMPGraphView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeRight 37 | UIInterfaceOrientationLandscapeLeft 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /MMPGraphView/MMPGraph/MMPGraphTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MMPGraphTableViewCell.swift 3 | // My Track 4 | // 5 | // Created by Jason Loewy on 4/19/16. 6 | // Copyright © 2016 Jason Loewy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MMPGraphTableViewCell: UITableViewCell { 12 | 13 | static let reuseIdentifier = "MMP-Graph-Cell" 14 | static var token: dispatch_once_t = 0 15 | 16 | @IBOutlet var graphView: MMPGraphView! 17 | 18 | override func awakeFromNib() { 19 | super.awakeFromNib() 20 | 21 | print("AWAKE FROM NIB") 22 | graphView.titleAlignment = .Left 23 | graphView.currentlyLoading = true 24 | graphView.setNeedsDisplay() 25 | // Initialization code 26 | } 27 | 28 | /** 29 | Responsible for configuring the graph table cell for a current array of dataplots 30 | 31 | - parameter dataPlots: The MMPGraphDataPlot array that is going to fuel this MMPGraphTableView 32 | */ 33 | func configureWithData(dataPlots:[MMPGraphDataPlot], delegate:MMPGraphDelegate?) 34 | { 35 | graphView.delegate = delegate 36 | 37 | dispatch_once(&MMPGraphTableViewCell.token) { () -> Void in 38 | if 2 <= dataPlots.count { 39 | self.graphView.currentPlotIdx = 2 40 | } 41 | } 42 | graphView.finishLoading(dataPlots) 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /MMPGraphView/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /MMPGraphView/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MMPGraphView 4 | // 5 | // Created by Jason Loewy on 4/10/16. 6 | // Copyright © 2016 My Macros+, LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /MMPGraphView/MMPGraph/MMPGraphViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MMPGraphViewController.swift 3 | // MMPGraphView 4 | // 5 | // Created by Jason Loewy on 4/18/16. 6 | // Copyright © 2016 My Macros+, LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MMPGraphViewController: UIViewController { 12 | 13 | private var graphView = MMPGraphView.newLoadingGraphView(CGRectZero, delegate: nil) 14 | let dataPlots:[MMPGraphDataPlot] 15 | let closeButton = UIButton(type: .Custom) 16 | 17 | init(graphView:MMPGraphView) 18 | { 19 | self.graphView.copyGraphAttributes(graphView) 20 | self.graphView.isFullScreen = true 21 | self.dataPlots = graphView.dataPlots 22 | super.init(nibName: nil, bundle: nil) 23 | } 24 | 25 | required init?(coder aDecoder: NSCoder) { 26 | fatalError("init(coder:) has not been implemented") 27 | } 28 | 29 | override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask { 30 | return .Landscape 31 | } 32 | 33 | override func prefersStatusBarHidden() -> Bool { 34 | return true 35 | } 36 | 37 | override func viewWillAppear(animated: Bool) { 38 | super.viewWillAppear(animated) 39 | UIApplication.sharedApplication().setStatusBarHidden(true, withAnimation: .Fade) 40 | } 41 | 42 | override func viewWillDisappear(animated: Bool) { 43 | super.viewWillAppear(animated) 44 | UIApplication.sharedApplication().setStatusBarHidden(false, withAnimation: .Fade) 45 | } 46 | 47 | override func viewDidLayoutSubviews() { 48 | graphView.setNeedsDisplay() 49 | graphView.layoutIfNeeded() 50 | } 51 | 52 | override func viewDidLoad() { 53 | super.viewDidLoad() 54 | graphView.finishLoading(dataPlots) 55 | graphView.allowFullScreen = false 56 | graphView.fullScreenButton?.removeFromSuperview() 57 | view.addSubview(graphView) 58 | 59 | // Add the close button 60 | closeButton.frame = CGRect(x: 20.0, y: 20.0, width: 15.0, height: 15.0) 61 | closeButton.autoresizingMask = [.FlexibleRightMargin, .FlexibleBottomMargin] 62 | closeButton.setBackgroundImage(UIImage(named: "MMPBackIcon")!, forState: .Normal) 63 | closeButton.addTarget(self, action: #selector(MMPGraphViewController.closeButtonTapped(_:)), forControlEvents: .TouchUpInside) 64 | view.addSubview(closeButton) 65 | } 66 | 67 | override func viewWillLayoutSubviews() { 68 | super.viewWillLayoutSubviews() 69 | graphView.frame = view.bounds 70 | } 71 | 72 | func closeButtonTapped(sender:UIButton) { 73 | dismissViewControllerAnimated(true, completion: nil) 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /MMPGraphView/MMPGraph/Data/MMPGraphDataSet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MMPGraphDataSet.swift 3 | // My Workout 4 | // 5 | // Created by Jason Loewy on 4/9/16. 6 | // Copyright © 2016 My Macros LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | The MMPGraphdataPlot is one full sheet of a graph. 13 | Contains all of the data sets that will be responsible for drawing the graph 14 | 15 | If there are more than one data set associated with this plot that means that there will be a segmented controller showing 16 | that allows for cycling between them 17 | */ 18 | class MMPGraphDataPlot: NSObject { 19 | 20 | var plotTitle = "" 21 | var primaryDataSet:MMPGraphDataSet 22 | var secondaryDataSet:MMPGraphDataSet? 23 | 24 | init(title:String, primaryDataSet:MMPGraphDataSet, secondaryDataSet:MMPGraphDataSet?) 25 | { 26 | self.plotTitle = title 27 | self.primaryDataSet = primaryDataSet 28 | self.secondaryDataSet = secondaryDataSet 29 | } 30 | } 31 | 32 | /** 33 | The MMPGraphDataSet is what drives a single line on the MMPGraphView 34 | It contains one set of datapoints and the title/color attributes associated with it 35 | */ 36 | class MMPGraphDataSet: NSObject { 37 | 38 | var dataTitle:String 39 | var dataPoints:[MMPGraphDataPoint] 40 | 41 | var color:UIColor 42 | 43 | init(title:String, dataPoints:[MMPGraphDataPoint], color:UIColor) 44 | { 45 | dataTitle = title 46 | self.dataPoints = dataPoints 47 | self.color = color 48 | } 49 | 50 | /** 51 | Responsible for getting the min and max values for a datasets 52 | 53 | - returns: A tuple of min max values 54 | */ 55 | func maxMinElement()->(minValue:MMPGraphDataPoint, maxValue:MMPGraphDataPoint) { 56 | 57 | var maxValue = dataPoints.first! 58 | var minValue = dataPoints.last! 59 | 60 | if maxValue.mmpGraphValue() < minValue.mmpGraphValue() { 61 | maxValue = minValue 62 | minValue = dataPoints.first! 63 | } 64 | 65 | for i in 0..<(dataPoints.count/2) 66 | { 67 | let valueA = dataPoints[i*2] 68 | let valueB = dataPoints[i*2+1] 69 | 70 | if valueA.mmpGraphValue() <= valueB.mmpGraphValue() { 71 | 72 | if (valueA.mmpGraphValue() < minValue.mmpGraphValue()) { 73 | minValue = valueA 74 | } 75 | 76 | if (valueB.mmpGraphValue() > maxValue.mmpGraphValue()) { 77 | maxValue = valueB 78 | } 79 | } 80 | else { 81 | 82 | if valueA.mmpGraphValue() > maxValue.mmpGraphValue() { 83 | maxValue = valueA 84 | } 85 | 86 | if valueB.mmpGraphValue() < minValue.mmpGraphValue() { 87 | minValue = valueB 88 | } 89 | } 90 | } 91 | return (minValue, maxValue) 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /MMPGraphView.xcodeproj/xcuserdata/jloewy.xcuserdatad/xcschemes/MMPGraphView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /MMPGraphView/MMPGraph/MMPGraphTableViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /MMPGraphView/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /MMPGraphView/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // MMPGraphView 4 | // 5 | // Created by Jason Loewy on 4/10/16. 6 | // Copyright © 2016 My Macros+, LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController, MMPGraphDelegate { 12 | 13 | @IBOutlet var storyboardGraphView: MMPGraphView! 14 | var programmaticGraphView:MMPGraphView! 15 | 16 | required init?(coder aDecoder: NSCoder) { 17 | super.init(coder: aDecoder) 18 | } 19 | 20 | override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask { 21 | return .Portrait 22 | } 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | 27 | // Create the first data set and associate it with the graph in the storyboard 28 | let primaryDataSet = MMPGraphDataSet(title: "Weight", 29 | dataPoints: [MMPTestData(title: "5", value: 365), 30 | MMPTestData(title: "8", value: 365), 31 | MMPTestData(title: "10", value: 385), 32 | MMPTestData(title: "7", value: 385), 33 | MMPTestData(title: "8", value: 405)], 34 | color: UIColor.whiteColor()) 35 | 36 | let secondaryDataSet = MMPGraphDataSet(title: "Reps", 37 | dataPoints: [MMPTestData(title: "5", value: 6), 38 | MMPTestData(title: "8", value: 4), 39 | MMPTestData(title: "10", value: 6), 40 | MMPTestData(title: "7", value: 4), 41 | MMPTestData(title: "8", value: 2)], 42 | color: UIColor.blueColor()) 43 | 44 | let dataPlot = MMPGraphDataPlot(title: "Weight vs Reps", primaryDataSet: primaryDataSet, secondaryDataSet:secondaryDataSet) 45 | storyboardGraphView.dataPlots = [dataPlot] 46 | 47 | 48 | // Create the second, manual graph view example 49 | let TWOprimaryDataSet = MMPGraphDataSet(title: "One", 50 | dataPoints: [MMPTestData(title: "5", value: 365), 51 | MMPTestData(title: "8", value: 365), 52 | MMPTestData(title: "10", value: 385), 53 | MMPTestData(title: "7", value: 385), 54 | MMPTestData(title: "8", value: 405)], 55 | color: UIColor.whiteColor()) 56 | 57 | let TWOsecondaryDataSet = MMPGraphDataSet(title: "Two", 58 | dataPoints: [MMPTestData(title: "5", value: 6), 59 | MMPTestData(title: "8", value: 4), 60 | MMPTestData(title: "10", value: 6), 61 | MMPTestData(title: "7", value: 4), 62 | MMPTestData(title: "8", value: 2)], 63 | color: UIColor.blueColor()) 64 | let TWOdataPlot = MMPGraphDataPlot(title: "Plot 1", primaryDataSet: TWOprimaryDataSet, secondaryDataSet:TWOsecondaryDataSet) 65 | 66 | let THRprimaryDataSet = MMPGraphDataSet(title: "Three", 67 | dataPoints: [MMPTestData(title: "1/10", value: 1029), 68 | MMPTestData(title: "1/11", value: 987), 69 | MMPTestData(title: "1/14", value: 350), 70 | MMPTestData(title: "1/18", value: 444), 71 | MMPTestData(title: "1/22", value: 605)], 72 | color: UIColor.whiteColor()) 73 | 74 | let THRsecondaryDataSet = MMPGraphDataSet(title: "Four", 75 | dataPoints: [MMPTestData(title: "5", value: 6), 76 | MMPTestData(title: "8", value: 4), 77 | MMPTestData(title: "10", value: 6), 78 | MMPTestData(title: "7", value: 4), 79 | MMPTestData(title: "8", value: 2)], 80 | color: UIColor.blueColor()) 81 | let THRdataPlot = MMPGraphDataPlot(title: "Plot 2", primaryDataSet: THRsecondaryDataSet, secondaryDataSet:THRprimaryDataSet) 82 | 83 | let FVprimaryDataSet = MMPGraphDataSet(title: "Five", 84 | dataPoints: [MMPTestData(title: "5", value: 6), 85 | MMPTestData(title: "8", value: 4), 86 | MMPTestData(title: "10", value: 6), 87 | MMPTestData(title: "7", value: 4), 88 | MMPTestData(title: "8", value: 2)], 89 | color: UIColor.whiteColor()) 90 | let FVdataPlot = MMPGraphDataPlot(title: "Single Plot", primaryDataSet: FVprimaryDataSet, secondaryDataSet:nil) 91 | 92 | 93 | 94 | let graphFrame = CGRect(x: 20.0, y: CGRectGetMaxY(storyboardGraphView.frame) + 50.0, width: CGRectGetWidth(self.view.bounds)-40.0, height: 300) 95 | // programmaticGraphView = MMPGraphView.newGraphView(graphFrame, dataPlots: [TWOdataPlot, THRdataPlot], delegate: nil) 96 | 97 | programmaticGraphView = MMPGraphView.newLoadingGraphView(graphFrame, delegate: self) 98 | programmaticGraphView.titleAlignment = .Left 99 | view.addSubview(programmaticGraphView) 100 | 101 | let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC))) 102 | dispatch_after(delayTime, dispatch_get_main_queue()) { 103 | self.programmaticGraphView.finishLoading([TWOdataPlot, THRdataPlot, FVdataPlot]) 104 | } 105 | 106 | storyboardGraphView.layer.cornerRadius = 6.0 107 | storyboardGraphView.clipsToBounds = true 108 | programmaticGraphView.layer.cornerRadius = 6.0 109 | programmaticGraphView.clipsToBounds = true 110 | } 111 | 112 | // MARK: - MMPGraphDelegate Methods 113 | 114 | func showFullScreenGraph(graphView: MMPGraphView) { 115 | 116 | let graphViewController = MMPGraphViewController(graphView: graphView) // MMPGraphViewController(dataPlots: graphView.dataPlots) 117 | showViewController(graphViewController, sender: self) 118 | 119 | } 120 | } 121 | 122 | -------------------------------------------------------------------------------- /MMPGraphView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 030A3E401CC9CA77004DE83A /* MMPGraphTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030A3E3E1CC9CA77004DE83A /* MMPGraphTableViewCell.swift */; }; 11 | 030A3E411CC9CA77004DE83A /* MMPGraphTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 030A3E3F1CC9CA77004DE83A /* MMPGraphTableViewCell.xib */; }; 12 | 0319604E1CC5F29200A56782 /* MMPGraphViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0319604D1CC5F29200A56782 /* MMPGraphViewController.swift */; }; 13 | 031960521CC5FC2D00A56782 /* MMPMedia.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 031960511CC5FC2D00A56782 /* MMPMedia.xcassets */; }; 14 | 037ABD671CC5BB7A00BE05CC /* MMPGraphView+Visuals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037ABD661CC5BB7A00BE05CC /* MMPGraphView+Visuals.swift */; }; 15 | 037ABD6D1CC5C0CF00BE05CC /* MMPGraphDataPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037ABD6A1CC5C0CF00BE05CC /* MMPGraphDataPoint.swift */; }; 16 | 037ABD6E1CC5C0CF00BE05CC /* MMPGraphDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037ABD6B1CC5C0CF00BE05CC /* MMPGraphDataSet.swift */; }; 17 | 037ABD6F1CC5C0CF00BE05CC /* MMPGraphDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037ABD6C1CC5C0CF00BE05CC /* MMPGraphDelegate.swift */; }; 18 | 037D04AE1CBB694700AE0C83 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037D04AD1CBB694700AE0C83 /* AppDelegate.swift */; }; 19 | 037D04B01CBB694700AE0C83 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037D04AF1CBB694700AE0C83 /* ViewController.swift */; }; 20 | 037D04B31CBB694700AE0C83 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 037D04B11CBB694700AE0C83 /* Main.storyboard */; }; 21 | 037D04B51CBB694700AE0C83 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 037D04B41CBB694700AE0C83 /* Assets.xcassets */; }; 22 | 037D04B81CBB694700AE0C83 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 037D04B61CBB694700AE0C83 /* LaunchScreen.storyboard */; }; 23 | 037D04CD1CBB697800AE0C83 /* MMPGraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037D04CA1CBB697800AE0C83 /* MMPGraphView.swift */; }; 24 | 037D04D11CBB6A9A00AE0C83 /* MMPTestData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037D04D01CBB6A9A00AE0C83 /* MMPTestData.swift */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 030A3E3E1CC9CA77004DE83A /* MMPGraphTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MMPGraphTableViewCell.swift; sourceTree = ""; }; 29 | 030A3E3F1CC9CA77004DE83A /* MMPGraphTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MMPGraphTableViewCell.xib; sourceTree = ""; }; 30 | 0319604D1CC5F29200A56782 /* MMPGraphViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MMPGraphViewController.swift; sourceTree = ""; }; 31 | 031960511CC5FC2D00A56782 /* MMPMedia.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = MMPMedia.xcassets; path = Resources/MMPMedia.xcassets; sourceTree = ""; }; 32 | 037ABD661CC5BB7A00BE05CC /* MMPGraphView+Visuals.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "MMPGraphView+Visuals.swift"; path = "Extensions/MMPGraphView+Visuals.swift"; sourceTree = ""; }; 33 | 037ABD6A1CC5C0CF00BE05CC /* MMPGraphDataPoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MMPGraphDataPoint.swift; path = Data/MMPGraphDataPoint.swift; sourceTree = ""; }; 34 | 037ABD6B1CC5C0CF00BE05CC /* MMPGraphDataSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MMPGraphDataSet.swift; path = Data/MMPGraphDataSet.swift; sourceTree = ""; }; 35 | 037ABD6C1CC5C0CF00BE05CC /* MMPGraphDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MMPGraphDelegate.swift; path = Data/MMPGraphDelegate.swift; sourceTree = ""; }; 36 | 037D04AA1CBB694700AE0C83 /* MMPGraphView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MMPGraphView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 37 | 037D04AD1CBB694700AE0C83 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 38 | 037D04AF1CBB694700AE0C83 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 39 | 037D04B21CBB694700AE0C83 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 40 | 037D04B41CBB694700AE0C83 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 41 | 037D04B71CBB694700AE0C83 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 42 | 037D04B91CBB694700AE0C83 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 43 | 037D04CA1CBB697800AE0C83 /* MMPGraphView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MMPGraphView.swift; sourceTree = ""; }; 44 | 037D04D01CBB6A9A00AE0C83 /* MMPTestData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MMPTestData.swift; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 037D04A71CBB694700AE0C83 /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 031960501CC5FBF600A56782 /* Resources */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 031960511CC5FC2D00A56782 /* MMPMedia.xcassets */, 62 | ); 63 | name = Resources; 64 | sourceTree = ""; 65 | }; 66 | 037ABD681CC5BB9B00BE05CC /* Extensions */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 037ABD661CC5BB7A00BE05CC /* MMPGraphView+Visuals.swift */, 70 | ); 71 | name = Extensions; 72 | sourceTree = ""; 73 | }; 74 | 037ABD691CC5C0C300BE05CC /* Data */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 037ABD6A1CC5C0CF00BE05CC /* MMPGraphDataPoint.swift */, 78 | 037ABD6B1CC5C0CF00BE05CC /* MMPGraphDataSet.swift */, 79 | 037ABD6C1CC5C0CF00BE05CC /* MMPGraphDelegate.swift */, 80 | ); 81 | name = Data; 82 | sourceTree = ""; 83 | }; 84 | 037D04A11CBB694700AE0C83 = { 85 | isa = PBXGroup; 86 | children = ( 87 | 037D04AC1CBB694700AE0C83 /* MMPGraphView */, 88 | 037D04C71CBB697800AE0C83 /* MMPGraph */, 89 | 037D04AB1CBB694700AE0C83 /* Products */, 90 | ); 91 | sourceTree = ""; 92 | }; 93 | 037D04AB1CBB694700AE0C83 /* Products */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 037D04AA1CBB694700AE0C83 /* MMPGraphView.app */, 97 | ); 98 | name = Products; 99 | sourceTree = ""; 100 | }; 101 | 037D04AC1CBB694700AE0C83 /* MMPGraphView */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 037D04AD1CBB694700AE0C83 /* AppDelegate.swift */, 105 | 037D04AF1CBB694700AE0C83 /* ViewController.swift */, 106 | 037D04B11CBB694700AE0C83 /* Main.storyboard */, 107 | 037D04B41CBB694700AE0C83 /* Assets.xcassets */, 108 | 037D04B61CBB694700AE0C83 /* LaunchScreen.storyboard */, 109 | 037D04B91CBB694700AE0C83 /* Info.plist */, 110 | 037D04D01CBB6A9A00AE0C83 /* MMPTestData.swift */, 111 | ); 112 | path = MMPGraphView; 113 | sourceTree = ""; 114 | }; 115 | 037D04C71CBB697800AE0C83 /* MMPGraph */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 037D04CA1CBB697800AE0C83 /* MMPGraphView.swift */, 119 | 0319604D1CC5F29200A56782 /* MMPGraphViewController.swift */, 120 | 030A3E3E1CC9CA77004DE83A /* MMPGraphTableViewCell.swift */, 121 | 030A3E3F1CC9CA77004DE83A /* MMPGraphTableViewCell.xib */, 122 | 037ABD691CC5C0C300BE05CC /* Data */, 123 | 037ABD681CC5BB9B00BE05CC /* Extensions */, 124 | 031960501CC5FBF600A56782 /* Resources */, 125 | ); 126 | name = MMPGraph; 127 | path = MMPGraphView/MMPGraph; 128 | sourceTree = ""; 129 | }; 130 | /* End PBXGroup section */ 131 | 132 | /* Begin PBXNativeTarget section */ 133 | 037D04A91CBB694700AE0C83 /* MMPGraphView */ = { 134 | isa = PBXNativeTarget; 135 | buildConfigurationList = 037D04BC1CBB694700AE0C83 /* Build configuration list for PBXNativeTarget "MMPGraphView" */; 136 | buildPhases = ( 137 | 037D04A61CBB694700AE0C83 /* Sources */, 138 | 037D04A71CBB694700AE0C83 /* Frameworks */, 139 | 037D04A81CBB694700AE0C83 /* Resources */, 140 | ); 141 | buildRules = ( 142 | ); 143 | dependencies = ( 144 | ); 145 | name = MMPGraphView; 146 | productName = MMPGraphView; 147 | productReference = 037D04AA1CBB694700AE0C83 /* MMPGraphView.app */; 148 | productType = "com.apple.product-type.application"; 149 | }; 150 | /* End PBXNativeTarget section */ 151 | 152 | /* Begin PBXProject section */ 153 | 037D04A21CBB694700AE0C83 /* Project object */ = { 154 | isa = PBXProject; 155 | attributes = { 156 | LastSwiftUpdateCheck = 0730; 157 | LastUpgradeCheck = 0730; 158 | ORGANIZATIONNAME = "My Macros+, LLC"; 159 | TargetAttributes = { 160 | 037D04A91CBB694700AE0C83 = { 161 | CreatedOnToolsVersion = 7.3; 162 | }; 163 | }; 164 | }; 165 | buildConfigurationList = 037D04A51CBB694700AE0C83 /* Build configuration list for PBXProject "MMPGraphView" */; 166 | compatibilityVersion = "Xcode 3.2"; 167 | developmentRegion = English; 168 | hasScannedForEncodings = 0; 169 | knownRegions = ( 170 | en, 171 | Base, 172 | ); 173 | mainGroup = 037D04A11CBB694700AE0C83; 174 | productRefGroup = 037D04AB1CBB694700AE0C83 /* Products */; 175 | projectDirPath = ""; 176 | projectRoot = ""; 177 | targets = ( 178 | 037D04A91CBB694700AE0C83 /* MMPGraphView */, 179 | ); 180 | }; 181 | /* End PBXProject section */ 182 | 183 | /* Begin PBXResourcesBuildPhase section */ 184 | 037D04A81CBB694700AE0C83 /* Resources */ = { 185 | isa = PBXResourcesBuildPhase; 186 | buildActionMask = 2147483647; 187 | files = ( 188 | 037D04B81CBB694700AE0C83 /* LaunchScreen.storyboard in Resources */, 189 | 037D04B51CBB694700AE0C83 /* Assets.xcassets in Resources */, 190 | 037D04B31CBB694700AE0C83 /* Main.storyboard in Resources */, 191 | 030A3E411CC9CA77004DE83A /* MMPGraphTableViewCell.xib in Resources */, 192 | 031960521CC5FC2D00A56782 /* MMPMedia.xcassets in Resources */, 193 | ); 194 | runOnlyForDeploymentPostprocessing = 0; 195 | }; 196 | /* End PBXResourcesBuildPhase section */ 197 | 198 | /* Begin PBXSourcesBuildPhase section */ 199 | 037D04A61CBB694700AE0C83 /* Sources */ = { 200 | isa = PBXSourcesBuildPhase; 201 | buildActionMask = 2147483647; 202 | files = ( 203 | 037ABD6F1CC5C0CF00BE05CC /* MMPGraphDelegate.swift in Sources */, 204 | 037D04B01CBB694700AE0C83 /* ViewController.swift in Sources */, 205 | 0319604E1CC5F29200A56782 /* MMPGraphViewController.swift in Sources */, 206 | 037D04CD1CBB697800AE0C83 /* MMPGraphView.swift in Sources */, 207 | 037ABD6D1CC5C0CF00BE05CC /* MMPGraphDataPoint.swift in Sources */, 208 | 030A3E401CC9CA77004DE83A /* MMPGraphTableViewCell.swift in Sources */, 209 | 037D04D11CBB6A9A00AE0C83 /* MMPTestData.swift in Sources */, 210 | 037D04AE1CBB694700AE0C83 /* AppDelegate.swift in Sources */, 211 | 037ABD671CC5BB7A00BE05CC /* MMPGraphView+Visuals.swift in Sources */, 212 | 037ABD6E1CC5C0CF00BE05CC /* MMPGraphDataSet.swift in Sources */, 213 | ); 214 | runOnlyForDeploymentPostprocessing = 0; 215 | }; 216 | /* End PBXSourcesBuildPhase section */ 217 | 218 | /* Begin PBXVariantGroup section */ 219 | 037D04B11CBB694700AE0C83 /* Main.storyboard */ = { 220 | isa = PBXVariantGroup; 221 | children = ( 222 | 037D04B21CBB694700AE0C83 /* Base */, 223 | ); 224 | name = Main.storyboard; 225 | sourceTree = ""; 226 | }; 227 | 037D04B61CBB694700AE0C83 /* LaunchScreen.storyboard */ = { 228 | isa = PBXVariantGroup; 229 | children = ( 230 | 037D04B71CBB694700AE0C83 /* Base */, 231 | ); 232 | name = LaunchScreen.storyboard; 233 | sourceTree = ""; 234 | }; 235 | /* End PBXVariantGroup section */ 236 | 237 | /* Begin XCBuildConfiguration section */ 238 | 037D04BA1CBB694700AE0C83 /* Debug */ = { 239 | isa = XCBuildConfiguration; 240 | buildSettings = { 241 | ALWAYS_SEARCH_USER_PATHS = NO; 242 | CLANG_ANALYZER_NONNULL = YES; 243 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 244 | CLANG_CXX_LIBRARY = "libc++"; 245 | CLANG_ENABLE_MODULES = YES; 246 | CLANG_ENABLE_OBJC_ARC = YES; 247 | CLANG_WARN_BOOL_CONVERSION = YES; 248 | CLANG_WARN_CONSTANT_CONVERSION = YES; 249 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 250 | CLANG_WARN_EMPTY_BODY = YES; 251 | CLANG_WARN_ENUM_CONVERSION = YES; 252 | CLANG_WARN_INT_CONVERSION = YES; 253 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 254 | CLANG_WARN_UNREACHABLE_CODE = YES; 255 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 256 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 257 | COPY_PHASE_STRIP = NO; 258 | DEBUG_INFORMATION_FORMAT = dwarf; 259 | ENABLE_STRICT_OBJC_MSGSEND = YES; 260 | ENABLE_TESTABILITY = YES; 261 | GCC_C_LANGUAGE_STANDARD = gnu99; 262 | GCC_DYNAMIC_NO_PIC = NO; 263 | GCC_NO_COMMON_BLOCKS = YES; 264 | GCC_OPTIMIZATION_LEVEL = 0; 265 | GCC_PREPROCESSOR_DEFINITIONS = ( 266 | "DEBUG=1", 267 | "$(inherited)", 268 | ); 269 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 270 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 271 | GCC_WARN_UNDECLARED_SELECTOR = YES; 272 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 273 | GCC_WARN_UNUSED_FUNCTION = YES; 274 | GCC_WARN_UNUSED_VARIABLE = YES; 275 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 276 | MTL_ENABLE_DEBUG_INFO = YES; 277 | ONLY_ACTIVE_ARCH = YES; 278 | SDKROOT = iphoneos; 279 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 280 | }; 281 | name = Debug; 282 | }; 283 | 037D04BB1CBB694700AE0C83 /* Release */ = { 284 | isa = XCBuildConfiguration; 285 | buildSettings = { 286 | ALWAYS_SEARCH_USER_PATHS = NO; 287 | CLANG_ANALYZER_NONNULL = YES; 288 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 289 | CLANG_CXX_LIBRARY = "libc++"; 290 | CLANG_ENABLE_MODULES = YES; 291 | CLANG_ENABLE_OBJC_ARC = YES; 292 | CLANG_WARN_BOOL_CONVERSION = YES; 293 | CLANG_WARN_CONSTANT_CONVERSION = YES; 294 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 295 | CLANG_WARN_EMPTY_BODY = YES; 296 | CLANG_WARN_ENUM_CONVERSION = YES; 297 | CLANG_WARN_INT_CONVERSION = YES; 298 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 299 | CLANG_WARN_UNREACHABLE_CODE = YES; 300 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 301 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 302 | COPY_PHASE_STRIP = NO; 303 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 304 | ENABLE_NS_ASSERTIONS = NO; 305 | ENABLE_STRICT_OBJC_MSGSEND = YES; 306 | GCC_C_LANGUAGE_STANDARD = gnu99; 307 | GCC_NO_COMMON_BLOCKS = YES; 308 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 309 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 310 | GCC_WARN_UNDECLARED_SELECTOR = YES; 311 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 312 | GCC_WARN_UNUSED_FUNCTION = YES; 313 | GCC_WARN_UNUSED_VARIABLE = YES; 314 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 315 | MTL_ENABLE_DEBUG_INFO = NO; 316 | SDKROOT = iphoneos; 317 | VALIDATE_PRODUCT = YES; 318 | }; 319 | name = Release; 320 | }; 321 | 037D04BD1CBB694700AE0C83 /* Debug */ = { 322 | isa = XCBuildConfiguration; 323 | buildSettings = { 324 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 325 | INFOPLIST_FILE = MMPGraphView/Info.plist; 326 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 327 | PRODUCT_BUNDLE_IDENTIFIER = com.macros.MMPGraphView; 328 | PRODUCT_NAME = "$(TARGET_NAME)"; 329 | }; 330 | name = Debug; 331 | }; 332 | 037D04BE1CBB694700AE0C83 /* Release */ = { 333 | isa = XCBuildConfiguration; 334 | buildSettings = { 335 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 336 | INFOPLIST_FILE = MMPGraphView/Info.plist; 337 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 338 | PRODUCT_BUNDLE_IDENTIFIER = com.macros.MMPGraphView; 339 | PRODUCT_NAME = "$(TARGET_NAME)"; 340 | }; 341 | name = Release; 342 | }; 343 | /* End XCBuildConfiguration section */ 344 | 345 | /* Begin XCConfigurationList section */ 346 | 037D04A51CBB694700AE0C83 /* Build configuration list for PBXProject "MMPGraphView" */ = { 347 | isa = XCConfigurationList; 348 | buildConfigurations = ( 349 | 037D04BA1CBB694700AE0C83 /* Debug */, 350 | 037D04BB1CBB694700AE0C83 /* Release */, 351 | ); 352 | defaultConfigurationIsVisible = 0; 353 | defaultConfigurationName = Release; 354 | }; 355 | 037D04BC1CBB694700AE0C83 /* Build configuration list for PBXNativeTarget "MMPGraphView" */ = { 356 | isa = XCConfigurationList; 357 | buildConfigurations = ( 358 | 037D04BD1CBB694700AE0C83 /* Debug */, 359 | 037D04BE1CBB694700AE0C83 /* Release */, 360 | ); 361 | defaultConfigurationIsVisible = 0; 362 | defaultConfigurationName = Release; 363 | }; 364 | /* End XCConfigurationList section */ 365 | }; 366 | rootObject = 037D04A21CBB694700AE0C83 /* Project object */; 367 | } 368 | -------------------------------------------------------------------------------- /MMPGraphView/MMPGraph/MMPGraphView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MMPGraphView.swift 3 | // My Track 4 | // 5 | // Created by Jason Loewy on 3/15/15. 6 | // Copyright (c) 2015 Jason Loewy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension NSString 12 | { 13 | /** 14 | Responsible for returning a single precision representation of the provided CGFloat 15 | 16 | - parameter currentFloat: The CGFloat being represented 17 | - parameter trim: A boolean flag that tells if you want to trim any trailing .0 or not 18 | 19 | - returns: a string that representation of the cgfloat in the parameters 20 | */ 21 | static func singlePrecisionFloat(currentFloat:CGFloat, attemptToTrim trim:Bool)->String { 22 | 23 | let string = NSString(format: "%.1f", currentFloat) 24 | return (trim && string.containsString(".0")) ? string.stringByReplacingOccurrencesOfString(".0", withString: "") : string as String 25 | } 26 | 27 | } 28 | 29 | @IBDesignable class MMPGraphView: UIView { 30 | 31 | // Visual Configuration Objects 32 | var graphInsetFrame = CGRect(x: 0, y: 0, width: 0, height: 0) 33 | @IBInspectable var startColor:UIColor = UIColor(red: 128.0/255.0, green: 182.0/255.0, blue: 248.0/255.0, alpha: 1.0) 34 | @IBInspectable var endColor:UIColor = UIColor(red: 50.0/255.0, green: 118.0/255.0, blue: 255.0/255.0, alpha: 1.0) 35 | 36 | // Loading Variables 37 | @IBInspectable var currentlyLoading = false 38 | let loadingActivityView = UIActivityIndicatorView(activityIndicatorStyle: .WhiteLarge) 39 | let loadingDimView = UIView(frame: CGRectZero) 40 | 41 | // Graph Interaction Objects 42 | var delegate:MMPGraphDelegate?; 43 | @IBInspectable var showTouchTitle = true 44 | @IBInspectable var allowFullScreen = true 45 | var fullScreenButton:UIButton? 46 | 47 | // Main Title Objects 48 | @IBInspectable var titleAlignment = NSTextAlignment.Center 49 | var titleLabel:UILabel = UILabel() 50 | var mainTitleText = NSAttributedString(string: "") 51 | var averageValueLabel = UILabel() 52 | 53 | // Graph State Objects 54 | var dataPlots = [MMPGraphDataPlot]() 55 | var currentPlotIdx = 0 56 | var plotSegmentedController:UISegmentedControl? 57 | var isFullScreen = false 58 | 59 | // MARK: - Initialize Methods 60 | 61 | /** 62 | Responsible for getting a new graph view 63 | 64 | - parameter frame: The frame of the graph view 65 | - parameter dataPoints: The datapoints that will be used in creating this graph 66 | - parameter delegate: The object that conforms to the delegate (optional) 67 | 68 | - returns: A newly initialized MMPGraphView 69 | */ 70 | static func newGraphView(frame: CGRect, dataPlots:[MMPGraphDataPlot], delegate:MMPGraphDelegate?)->MMPGraphView 71 | { 72 | return MMPGraphView(frame: frame, dataPlots: dataPlots, delegate: delegate) 73 | } 74 | 75 | static func newLoadingGraphView(frame:CGRect, delegate:MMPGraphDelegate?)->MMPGraphView 76 | { 77 | let graphView = MMPGraphView(frame: frame, dataPlots: [MMPGraphDataPlot](), delegate: delegate) 78 | graphView.currentlyLoading = true 79 | 80 | return graphView 81 | } 82 | 83 | private init(frame: CGRect, dataPlots:[MMPGraphDataPlot], delegate:MMPGraphDelegate?) { 84 | 85 | super.init(frame: frame) 86 | self.dataPlots = dataPlots 87 | self.delegate = delegate 88 | initializeSubViews() 89 | } 90 | 91 | required init?(coder aDecoder: NSCoder) { 92 | super.init(coder: aDecoder) 93 | self.dataPlots = [] 94 | initializeSubViews() 95 | } 96 | 97 | // Responsble for initializing all of the subviews associated with this graphview instance 98 | func initializeSubViews() 99 | { 100 | // Configure and add the title label 101 | if !subviews.contains(titleLabel) { 102 | titleLabel.font = MMPGraphView.regularFont() 103 | titleLabel.textColor = UIColor.whiteColor() 104 | titleLabel.textAlignment = titleAlignment 105 | titleLabel.attributedText = mainTitleText 106 | addSubview(titleLabel) 107 | } 108 | 109 | // Configure and add the average label 110 | if !subviews.contains(averageValueLabel) { 111 | averageValueLabel.font = MMPGraphView.regularFont(12.0) 112 | averageValueLabel.textColor = UIColor.whiteColor() 113 | averageValueLabel.textAlignment = titleAlignment 114 | averageValueLabel.text = "" 115 | insertSubview(averageValueLabel, belowSubview: titleLabel) 116 | } 117 | 118 | if plotSegmentedController == nil && dataPlots.count > 1 { 119 | 120 | // Initialize, configure and add the plot switching segmented controller 121 | var plotTitles = [String]() 122 | for currentPlot in dataPlots { 123 | plotTitles.append(currentPlot.plotTitle) 124 | } 125 | plotSegmentedController = UISegmentedControl(items: plotTitles) 126 | plotSegmentedController!.tintColor = UIColor.whiteColor() 127 | plotSegmentedController!.setTitleTextAttributes([NSFontAttributeName : MMPGraphView.lightFont()], forState: .Normal) 128 | plotSegmentedController!.setTitleTextAttributes([NSFontAttributeName : MMPGraphView.boldFont()], forState: .Selected) 129 | plotSegmentedController!.addTarget(self, action: #selector(MMPGraphView.activePlotValueChanged(_:)), forControlEvents: .ValueChanged) 130 | plotSegmentedController!.selectedSegmentIndex = 0 131 | addSubview(plotSegmentedController!) 132 | } 133 | 134 | if !subviews.contains(loadingDimView) { 135 | // Initialize all of the loading indicator objects 136 | loadingDimView.frame = bounds 137 | loadingDimView.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.5) 138 | loadingDimView.hidden = true 139 | 140 | loadingActivityView.tintColor = UIColor.whiteColor() 141 | loadingActivityView.hidesWhenStopped = true 142 | loadingDimView.addSubview(loadingActivityView) 143 | addSubview(loadingDimView) 144 | } 145 | 146 | if (allowFullScreen && fullScreenButton == nil) 147 | { 148 | let fullScreenButtonSide:CGFloat = 15.0 149 | 150 | // Initialize the full screen button and functionality 151 | fullScreenButton = UIButton(type: .Custom) 152 | fullScreenButton!.tintColor = UIColor.whiteColor() 153 | fullScreenButton!.frame = CGRect(x: 15.0, y: 15.0, width: fullScreenButtonSide, height: fullScreenButtonSide) 154 | 155 | fullScreenButton!.autoresizingMask = [.FlexibleRightMargin, .FlexibleBottomMargin] 156 | fullScreenButton?.setBackgroundImage(MMPGraphView.fullScreenImage(), forState: .Normal) 157 | fullScreenButton!.addTarget(self, action: #selector(MMPGraphView.fullScreenButtonTapped(_:)), forControlEvents: .TouchUpInside) 158 | addSubview(fullScreenButton!) 159 | } 160 | } 161 | 162 | /** 163 | Responsible for copying all of the state attributes from one graph view to another 164 | 165 | - parameter idealGraph: The MMPGraphView that you are copying the attribute values from 166 | */ 167 | func copyGraphAttributes(idealGraph:MMPGraphView) 168 | { 169 | titleAlignment = idealGraph.titleAlignment 170 | showTouchTitle = idealGraph.showTouchTitle 171 | currentlyLoading = idealGraph.currentlyLoading 172 | currentPlotIdx = idealGraph.currentPlotIdx 173 | } 174 | 175 | 176 | // MARK: - View Layout Methods 177 | 178 | func finishLoading(dataPlots:[MMPGraphDataPlot]) { 179 | self.dataPlots = dataPlots 180 | currentlyLoading = false 181 | initializeSubViews() 182 | setNeedsDisplay() 183 | } 184 | 185 | override func layoutSubviews() { 186 | 187 | super.layoutSubviews() 188 | let labelWidth = CGRectGetWidth(bounds) * 0.5 189 | let titleYPadding:CGFloat = isFullScreen ? 15.0 : 5.0 190 | let labelHeight:CGFloat = 20.0 191 | 192 | titleLabel.font = isFullScreen ? MMPGraphView.boldFont(18.0) : MMPGraphView.boldFont() 193 | titleLabel.textAlignment = titleAlignment 194 | let xOrigin = (titleAlignment == .Center) ? (CGRectGetWidth(bounds)*0.25) : max(graphInsetFrame.origin.x, 40.0) 195 | titleLabel.frame = CGRect(x: xOrigin, y: titleYPadding, width: labelWidth, height: labelHeight) 196 | averageValueLabel.frame = CGRect(x: xOrigin, y: CGRectGetMaxY(titleLabel.frame), width: labelWidth, height: 16.0) 197 | averageValueLabel.textAlignment = titleAlignment 198 | 199 | loadingDimView.frame = bounds 200 | loadingActivityView.center = CGPoint(x: loadingDimView.bounds.width/2.0, y: loadingDimView.bounds.height/2.0) 201 | } 202 | 203 | // Only override drawRect: if you perform custom drawing. 204 | // An empty implementation adversely affects performance during animation. 205 | override func drawRect(rect: CGRect) { 206 | 207 | fullScreenButton?.hidden = !allowFullScreen 208 | if currentPlotIdx < dataPlots.count 209 | { 210 | // Set up the title text 211 | // This is in draw rect becuase it needs to get reset everytime that the segmented controller value changes 212 | let titleFont = isFullScreen ? MMPGraphView.boldFont(18.0) : MMPGraphView.boldFont() 213 | if let secondaryData = dataPlots[currentPlotIdx].secondaryDataSet 214 | { 215 | let primaryDataSet = dataPlots[currentPlotIdx].primaryDataSet 216 | let titleAttributeText = NSMutableAttributedString(string: "\(primaryDataSet.dataTitle) vs \(secondaryData.dataTitle)", 217 | attributes: [NSFontAttributeName : titleFont, 218 | NSForegroundColorAttributeName : UIColor.whiteColor()]) 219 | titleAttributeText.addAttributes([NSForegroundColorAttributeName:primaryDataSet.color], range: NSMakeRange(0, primaryDataSet.dataTitle.characters.count)) 220 | titleAttributeText.addAttributes([NSForegroundColorAttributeName:secondaryData.color], range: NSMakeRange(primaryDataSet.dataTitle.characters.count + 4, secondaryData.dataTitle.characters.count)) 221 | mainTitleText = titleAttributeText 222 | } 223 | else { 224 | mainTitleText = NSAttributedString(string: "\(dataPlots[currentPlotIdx].primaryDataSet.dataTitle)", attributes: [NSFontAttributeName : titleFont, NSForegroundColorAttributeName : UIColor.whiteColor()]) 225 | } 226 | } 227 | else { 228 | mainTitleText = NSAttributedString(string: "") 229 | } 230 | titleLabel.attributedText = mainTitleText 231 | averageValueLabel.text = "" 232 | 233 | // Calculate the boundaries 234 | graphInsetFrame.origin.y = bounds.height * 0.25 235 | if dataPlots.count > 1 { 236 | graphInsetFrame.origin.y -= 20.0 237 | } 238 | 239 | graphInsetFrame.size.height = bounds.height - graphInsetFrame.origin.y*2.0 240 | if dataPlots.count > 1 { 241 | graphInsetFrame.size.height -= 40.0 242 | } 243 | graphInsetFrame.origin.x = bounds.width * 0.125 244 | graphInsetFrame.size.width = CGRectGetWidth(bounds) - (graphInsetFrame.origin.x * 2.0) 245 | let columnWidth:CGFloat = (currentPlotIdx < dataPlots.count) ? (graphInsetFrame.width / max(1,(CGFloat(dataPlots[currentPlotIdx].primaryDataSet.dataPoints.count)) - 1.0)) : 0 246 | 247 | // Now that you have the inset bounds calculated you can layout the segmented controller to sit right underneath it 248 | if let segmentedController = plotSegmentedController { 249 | 250 | let yOffset = ((CGRectGetHeight(bounds) - CGRectGetMaxY(graphInsetFrame)) / 2.0) - (CGRectGetHeight(segmentedController.bounds) / 4.0) 251 | if !isFullScreen { 252 | segmentedController.frame = CGRect(x: 15.0, y: CGRectGetMaxY(graphInsetFrame) + yOffset, width: bounds.width - 30.0, height: segmentedController.frame.size.height) 253 | } 254 | else { 255 | segmentedController.frame = CGRect(x: graphInsetFrame.origin.x, y: CGRectGetMaxY(graphInsetFrame) + yOffset, width: graphInsetFrame.size.width, height: segmentedController.frame.size.height) 256 | } 257 | } 258 | 259 | // Draw the background gradient 260 | let context:CGContext? = UIGraphicsGetCurrentContext() 261 | let colorRGB = CGColorSpaceCreateDeviceRGB() 262 | let gradiant = CGGradientCreateWithColors(colorRGB, 263 | [startColor.CGColor, endColor.CGColor], [0.0,1.0]) 264 | CGContextDrawLinearGradient(context, gradiant, CGPointZero, CGPoint(x: 0, y: self.bounds.height), .DrawsBeforeStartLocation) 265 | 266 | // MARK: - column X|Y positioning closures 267 | 268 | let columnXPosition = {(dataPoints:[MMPGraphDataPoint], xColumn:CGFloat)->CGFloat in 269 | return (dataPoints.count > 1) ? self.graphInsetFrame.origin.x + columnWidth*xColumn : CGRectGetWidth(self.bounds)/2.0 270 | } 271 | 272 | let columnYPosition = {(graphValue:CGFloat, minValue:MMPGraphDataPoint, maxValue:MMPGraphDataPoint)->CGFloat in 273 | 274 | let normalizedCurrentValue = graphValue - minValue.mmpGraphValue() 275 | let yPosition = (self.graphInsetFrame.size.height + self.graphInsetFrame.origin.y) - ((normalizedCurrentValue/(maxValue.mmpGraphValue() - minValue.mmpGraphValue())) * self.graphInsetFrame.size.height) 276 | return isnan(yPosition) ? self.graphInsetFrame.origin.y + self.graphInsetFrame.size.height/2.0 : yPosition 277 | } 278 | 279 | // Draw the actual data plots 280 | loadingDimView.hidden = true 281 | if currentPlotIdx < dataPlots.count && dataPlots[currentPlotIdx].primaryDataSet.dataPoints.count > 0 282 | { 283 | var dataSets = [self.dataPlots[currentPlotIdx].primaryDataSet]; 284 | if let secondDataSet = self.dataPlots[currentPlotIdx].secondaryDataSet { 285 | dataSets.append(secondDataSet) 286 | } 287 | 288 | var index = -1 289 | for currentDataSet in dataSets 290 | { 291 | index += 1 292 | let minMaxValues = currentDataSet.maxMinElement() 293 | let maxValue = minMaxValues.maxValue 294 | let minValue = minMaxValues.minValue 295 | 296 | // Move to the start add the main graph plot 297 | let graphPath = UIBezierPath() 298 | graphPath.moveToPoint(CGPoint(x: columnXPosition(currentDataSet.dataPoints, 0), 299 | y: columnYPosition(currentDataSet.dataPoints[0].mmpGraphValue(), minValue, maxValue))) 300 | for i in 1.. 0) ? "\(averageValueLabel.text!) | \(avgString)" : "Average: \(avgString)" 361 | 362 | // Draw the 3 horizontal lines and the indicator lablese 363 | let graphMidPtY = graphInsetFrame.origin.y + graphInsetFrame.size.height/2.0 364 | let averageWeight = (maxValue.mmpGraphValue() + minValue.mmpGraphValue())/2.0 365 | var bgLinePoints = [graphInsetFrame.origin.y+graphInsetFrame.size.height, graphMidPtY, graphInsetFrame.origin.y] 366 | var bgLabelValues:[NSString] = [NSString.singlePrecisionFloat(minValue.mmpGraphValue(), attemptToTrim: true), 367 | NSString.singlePrecisionFloat(averageWeight, attemptToTrim: true), 368 | NSString.singlePrecisionFloat(maxValue.mmpGraphValue(), attemptToTrim: true)] 369 | 370 | // Enter here if there are a lot of graph points and you could have more than just 3 lines 371 | if currentDataSet.dataPoints.count > 30 372 | { 373 | bgLinePoints.insert(graphMidPtY + graphInsetFrame.size.height/4.0, atIndex: 1) 374 | bgLabelValues.insert(NSString.singlePrecisionFloat((averageWeight + minValue.mmpGraphValue()) / 2.0, attemptToTrim: true), atIndex: 1) 375 | 376 | bgLinePoints.insert(graphMidPtY - graphInsetFrame.size.height/4.0, atIndex: 3) 377 | bgLabelValues.insert(NSString.singlePrecisionFloat((averageWeight + maxValue.mmpGraphValue()) / 2.0, attemptToTrim: true), atIndex: 3) 378 | } 379 | 380 | // MARK: - Draw side markers 381 | let bgLinePath = UIBezierPath() 382 | let bgLineWidth = bounds.width-graphInsetFrame.origin.x 383 | counter = 0 384 | for currentBGYPoint in bgLinePoints 385 | { 386 | bgLinePath.moveToPoint(CGPoint(x: graphInsetFrame.origin.x, y: currentBGYPoint)) 387 | bgLinePath.addLineToPoint(CGPoint(x: bgLineWidth, y: currentBGYPoint)) 388 | 389 | let currentLabel = UILabel() 390 | currentLabel.text = bgLabelValues[counter] as String 391 | counter += 1 392 | currentLabel.textColor = UIColor.whiteColor() 393 | currentLabel.font = MMPGraphView.lightFont() 394 | currentLabel.sizeToFit() 395 | 396 | // Primary data points are drawn on the right margin, secondary on the left 397 | let xOrigin = (index == 0) ? (bgLineWidth+10.0) : (graphInsetFrame.origin.x - (CGRectGetWidth(currentLabel.bounds) + 10.0)) 398 | currentLabel.drawTextInRect(CGRect(x: xOrigin, y: currentBGYPoint-10, width: bounds.width-(bgLineWidth + 5), height: 20)) 399 | } 400 | 401 | UIColor(white: 1.0, alpha: 0.5).setStroke() 402 | bgLinePath.lineWidth = 1.0 403 | bgLinePath.stroke() 404 | 405 | // Draw the x axis labels 406 | var previousXAxisFrame = CGRectZero 407 | let xLabelWidth:CGFloat = 80.0 408 | let xLabelCount:Int = min(currentDataSet.dataPoints.count, 6) 409 | if xLabelCount > 1 && index == 0 // Currently only draw the primary data plots x label 410 | { 411 | for i in 0.., withEvent event: UIEvent?) { 471 | handleTouch(touches, withEvent: event) 472 | } 473 | 474 | override func touchesMoved(touches: Set, withEvent event: UIEvent?) { 475 | handleTouch(touches, withEvent: event) 476 | } 477 | 478 | /// Responsible for reacting to the touch inside of the graph inset view - Updates the top label 479 | func handleTouch(touches: Set, withEvent event:UIEvent?) { 480 | 481 | if showTouchTitle { 482 | 483 | // If the touch point is in the drawn graph (or a little outside of it) handle it as a data touch 484 | let locationPoint = touches.first!.locationInView(self) 485 | let touchArea = CGRectInset(graphInsetFrame, -10, -10) 486 | if CGRectContainsPoint(touchArea, locationPoint) 487 | { 488 | let normalizedX = locationPoint.x - touchArea.origin.x 489 | let insetPercentage = normalizedX / touchArea.size.width 490 | let index = Int(floor(CGFloat(dataPlots[currentPlotIdx].primaryDataSet.dataPoints.count) * insetPercentage)) 491 | let dataPointTouched = dataPlots[currentPlotIdx].primaryDataSet.dataPoints[index] 492 | 493 | titleLabel.attributedText = NSAttributedString(string: "\(dataPointTouched.mmpGraphTitle()):\(dataPointTouched.mmpGraphValue())") 494 | } 495 | else { 496 | titleLabel.attributedText = mainTitleText 497 | } 498 | } 499 | } 500 | 501 | /// Responsible for reacting to the user tapping the full screen presentation button 502 | func fullScreenButtonTapped(sender:UIButton) 503 | { 504 | delegate?.showFullScreenGraph?(self) 505 | } 506 | } 507 | --------------------------------------------------------------------------------