├── README.md ├── SwiftCharts.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── SwiftCharts └── LineChart.swift ├── SwiftChartsMac ├── AppDelegate.swift ├── Base.lproj │ └── Main.storyboard ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist └── View Controllers │ ├── AdvancedLineChartViewController.swift │ ├── DynamicLineChartViewController.swift │ ├── LabeledLineChartViewController.swift │ ├── MultiAxisLineChartViewController.swift │ └── SimpleLineChartViewController.swift └── SwiftChartsiOS ├── AppDelegate.swift ├── Base.lproj ├── LaunchScreen.xib └── Main.storyboard ├── Images.xcassets ├── AppIcon.appiconset │ └── Contents.json ├── first.imageset │ ├── Contents.json │ └── first.pdf └── second.imageset │ ├── Contents.json │ └── second.pdf ├── Info.plist └── View Controllers ├── AdvancedLineChartViewController.swift ├── DynamicLineChartViewController.swift ├── LabeledLineChartViewController.swift ├── MultiAxisLineChartViewController.swift └── SimpleLineChartViewController.swift /README.md: -------------------------------------------------------------------------------- 1 | # SwiftCharts 2 | SwiftCharts is a lightweight "library" for Line Charts (and more in the future!). 3 | 4 | ```swift 5 | let view = LineChartView() 6 | view.lineChart.datasets = [ LineChart.Dataset([3.0, 4.0, 9.0, 11.0, 15.0, 13.0]) ] 7 | ``` 8 | ![Simple Example](http://kevinbrewster.github.io/SwiftCharts/images/simple_example.png) 9 | 10 | # Installation 11 | 1. Download just the [LineChart.swift](https://raw.githubusercontent.com/kevinbrewster/SwiftCharts/master/SwiftCharts/LineChart.swift) file, or download the [example project](https://github.com/kevinbrewster/SwiftCharts/zipball/master) (which includes various examples for both Mac and iOS). 12 | 2. Drag LineChart.swift into your Xcode project 13 | 14 | # Examples 15 | 16 | ### Advanced Example 17 | 18 | Each line can be completely customized (fill color, stroke color, point shape/color/stroke, bezier curves, etc) 19 | ```swift 20 | let view = LineChartView() 21 | 22 | let data1: [CGFloat] = [65.0, 59.0, 80.0, 81.0, 56.0, 55.0, 40.0] 23 | let dataset1 = LineChart.Dataset(data1, label: "Data 1") 24 | dataset1.color = NSColor.redColor().CGColor 25 | dataset1.fillColor = nil 26 | dataset1.curve = .Bezier(0.3) 27 | 28 | let data2: [CGFloat] = [28.0, 48.0, 40.0, 19.0, 86.0, 27.0, 90.0] 29 | let dataset2 = LineChart.Dataset(data2, label: "Data 2") 30 | dataset2.color = NSColor.blueColor().CGColor 31 | dataset2.curve = .Bezier(0.3) 32 | 33 | lineChart.datasets = [ dataset1, dataset2 ] 34 | ``` 35 | ![Advanced Example](http://kevinbrewster.github.io/SwiftCharts/images/advanced_example.png) 36 | 37 | ### Labeled Example 38 | 39 | Custom labels can be added for the x-axis 40 | ```swift 41 | let view = LineChartView() 42 | 43 | lineChart.xAxis.labels = ["January", "February", "March", "April", "June", "July", "August", "September", "October", "November", "December"] 44 | 45 | var data: [CGFloat] = [0.003, 0.004, 0.009, 0.011, 0.013, 0.015, 0.004, 0.003, 0.009, 0.0075, 0.0061] 46 | lineChart.datasets = [ LineChart.Dataset(data, label: "My Data") ] 47 | ``` 48 | ![Labeled Example](http://kevinbrewster.github.io/SwiftCharts/images/labeled_example.png) 49 | 50 | ### Dynamic Example 51 | 52 | LineChart automatically detects updated values and animates the graph accordingly. 53 | ```swift 54 | let view = LineChartView() 55 | let line = LineChart.Dataset([3.0, 4.0, 9.0, 11.0, 13.0, 15.0]) 56 | view.lineChart.datasets = [ line ] 57 | 58 | // below could happen at any point 59 | let newRandomValue = arc4random() % 25 60 | line.data += [ CGFloat(newRandomValue) ] 61 | ``` 62 | ![Dynamic Example](http://kevinbrewster.github.io/SwiftCharts/images/dynamic_example.png) 63 | 64 | ### Multi-Axis Example 65 | 66 | You can assign datasets to different axes, each with their own scale 67 | ```swift 68 | let view = LineChartView() 69 | 70 | var data1: [CGFloat] = [3.0, 4.0, 9.0, 11.0, 13.0, 15.0] 71 | lineChart.datasets += [ LineChart.Dataset(data1, label: "One") ] 72 | 73 | var axis2 = LineChart.Axis(alignment: .Right) 74 | var data2: [CGFloat] = [504040.0, 201050.0, 303001.0, 130049.0, 170021.0, 202003.0] 75 | lineChart.datasets += [ LineChart.Dataset(data2, label: "Two", yAxis: axis2) ] 76 | 77 | var axis3 = LineChart.Axis(alignment: .Right) 78 | var data3: [CGFloat] = [0.0021, 0.0056, 0.001, 0.003, 0.005, 0.002]; 79 | lineChart.datasets += [ LineChart.Dataset(data3, label: "Three", yAxis: axis3) ] 80 | ``` 81 | ![Multiaxis Example](http://kevinbrewster.github.io/SwiftCharts/images/multiaxis_example.png) 82 | -------------------------------------------------------------------------------- /SwiftCharts.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 677C59841A6F475200110D1C /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 677C59831A6F475200110D1C /* Images.xcassets */; }; 11 | 67BA2EBE1A6F7EA7008CC3F3 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 67BA2EB31A6F7EA7008CC3F3 /* Images.xcassets */; }; 12 | 67BA2EC01A6F7EA7008CC3F3 /* AdvancedLineChartViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67BA2EB61A6F7EA7008CC3F3 /* AdvancedLineChartViewController.swift */; }; 13 | 67BA2EC11A6F7EA7008CC3F3 /* DynamicLineChartViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67BA2EB71A6F7EA7008CC3F3 /* DynamicLineChartViewController.swift */; }; 14 | 67BA2EC21A6F7EA7008CC3F3 /* LabeledLineChartViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67BA2EB81A6F7EA7008CC3F3 /* LabeledLineChartViewController.swift */; }; 15 | 67BA2EC31A6F7EA7008CC3F3 /* MultiAxisLineChartViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67BA2EB91A6F7EA7008CC3F3 /* MultiAxisLineChartViewController.swift */; }; 16 | 67BA2EC41A6F7EA7008CC3F3 /* SimpleLineChartViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67BA2EBA1A6F7EA7008CC3F3 /* SimpleLineChartViewController.swift */; }; 17 | 67BA2ECA1A701955008CC3F3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67BA2EC91A701955008CC3F3 /* AppDelegate.swift */; }; 18 | 67BA2ECC1A70195F008CC3F3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67BA2ECB1A70195F008CC3F3 /* AppDelegate.swift */; }; 19 | 67BA2ED51A701AE4008CC3F3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 67BA2ED21A701AE4008CC3F3 /* Main.storyboard */; }; 20 | 67BA2ED81A701B25008CC3F3 /* LineChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67BA2ED71A701B25008CC3F3 /* LineChart.swift */; }; 21 | 67BA2ED91A701B25008CC3F3 /* LineChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67BA2ED71A701B25008CC3F3 /* LineChart.swift */; }; 22 | 67BA2EDC1A701BCA008CC3F3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 67BA2EDA1A701BCA008CC3F3 /* Main.storyboard */; }; 23 | 67BA2EDD1A701BCA008CC3F3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 67BA2EDA1A701BCA008CC3F3 /* Main.storyboard */; }; 24 | 67E756A41A6F78EB0078C523 /* AdvancedLineChartViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67E7569F1A6F78EB0078C523 /* AdvancedLineChartViewController.swift */; }; 25 | 67E756A51A6F78EB0078C523 /* DynamicLineChartViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67E756A01A6F78EB0078C523 /* DynamicLineChartViewController.swift */; }; 26 | 67E756A61A6F78EB0078C523 /* LabeledLineChartViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67E756A11A6F78EB0078C523 /* LabeledLineChartViewController.swift */; }; 27 | 67E756A71A6F78EB0078C523 /* MultiAxisLineChartViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67E756A21A6F78EB0078C523 /* MultiAxisLineChartViewController.swift */; }; 28 | 67E756A81A6F78EB0078C523 /* SimpleLineChartViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67E756A31A6F78EB0078C523 /* SimpleLineChartViewController.swift */; }; 29 | /* End PBXBuildFile section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 677C59501A6F473500110D1C /* SwiftChartsiOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftChartsiOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | 677C597B1A6F475200110D1C /* SwiftChartsMac.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftChartsMac.app; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | 677C597E1A6F475200110D1C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 35 | 677C59831A6F475200110D1C /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 36 | 67BA2EB01A6F7EA7008CC3F3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 37 | 67BA2EB31A6F7EA7008CC3F3 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 38 | 67BA2EB41A6F7EA7008CC3F3 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 39 | 67BA2EB61A6F7EA7008CC3F3 /* AdvancedLineChartViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdvancedLineChartViewController.swift; sourceTree = ""; }; 40 | 67BA2EB71A6F7EA7008CC3F3 /* DynamicLineChartViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicLineChartViewController.swift; sourceTree = ""; }; 41 | 67BA2EB81A6F7EA7008CC3F3 /* LabeledLineChartViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabeledLineChartViewController.swift; sourceTree = ""; }; 42 | 67BA2EB91A6F7EA7008CC3F3 /* MultiAxisLineChartViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultiAxisLineChartViewController.swift; sourceTree = ""; }; 43 | 67BA2EBA1A6F7EA7008CC3F3 /* SimpleLineChartViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleLineChartViewController.swift; sourceTree = ""; }; 44 | 67BA2EC91A701955008CC3F3 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 45 | 67BA2ECB1A70195F008CC3F3 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 46 | 67BA2ED31A701AE4008CC3F3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 47 | 67BA2ED71A701B25008CC3F3 /* LineChart.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineChart.swift; sourceTree = ""; }; 48 | 67BA2EDB1A701BCA008CC3F3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 49 | 67E7569F1A6F78EB0078C523 /* AdvancedLineChartViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdvancedLineChartViewController.swift; sourceTree = ""; }; 50 | 67E756A01A6F78EB0078C523 /* DynamicLineChartViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicLineChartViewController.swift; sourceTree = ""; }; 51 | 67E756A11A6F78EB0078C523 /* LabeledLineChartViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabeledLineChartViewController.swift; sourceTree = ""; }; 52 | 67E756A21A6F78EB0078C523 /* MultiAxisLineChartViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultiAxisLineChartViewController.swift; sourceTree = ""; }; 53 | 67E756A31A6F78EB0078C523 /* SimpleLineChartViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleLineChartViewController.swift; sourceTree = ""; }; 54 | /* End PBXFileReference section */ 55 | 56 | /* Begin PBXFrameworksBuildPhase section */ 57 | 677C594D1A6F473500110D1C /* Frameworks */ = { 58 | isa = PBXFrameworksBuildPhase; 59 | buildActionMask = 2147483647; 60 | files = ( 61 | ); 62 | runOnlyForDeploymentPostprocessing = 0; 63 | }; 64 | 677C59781A6F475200110D1C /* Frameworks */ = { 65 | isa = PBXFrameworksBuildPhase; 66 | buildActionMask = 2147483647; 67 | files = ( 68 | ); 69 | runOnlyForDeploymentPostprocessing = 0; 70 | }; 71 | /* End PBXFrameworksBuildPhase section */ 72 | 73 | /* Begin PBXGroup section */ 74 | 677C59471A6F473500110D1C = { 75 | isa = PBXGroup; 76 | children = ( 77 | 67BA2ED61A701B25008CC3F3 /* SwiftCharts */, 78 | 67BA2EAD1A6F7EA7008CC3F3 /* SwiftChartsiOS */, 79 | 677C597C1A6F475200110D1C /* SwiftChartsMac */, 80 | 677C59511A6F473500110D1C /* Products */, 81 | ); 82 | sourceTree = ""; 83 | }; 84 | 677C59511A6F473500110D1C /* Products */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 677C59501A6F473500110D1C /* SwiftChartsiOS.app */, 88 | 677C597B1A6F475200110D1C /* SwiftChartsMac.app */, 89 | ); 90 | name = Products; 91 | sourceTree = ""; 92 | }; 93 | 677C597C1A6F475200110D1C /* SwiftChartsMac */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 67E7569E1A6F78EB0078C523 /* View Controllers */, 97 | 677C597D1A6F475200110D1C /* Supporting Files */, 98 | 677C59831A6F475200110D1C /* Images.xcassets */, 99 | ); 100 | path = SwiftChartsMac; 101 | sourceTree = ""; 102 | }; 103 | 677C597D1A6F475200110D1C /* Supporting Files */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 67BA2ECB1A70195F008CC3F3 /* AppDelegate.swift */, 107 | 67BA2ED21A701AE4008CC3F3 /* Main.storyboard */, 108 | 677C597E1A6F475200110D1C /* Info.plist */, 109 | ); 110 | name = "Supporting Files"; 111 | sourceTree = ""; 112 | }; 113 | 67BA2EAD1A6F7EA7008CC3F3 /* SwiftChartsiOS */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 67BA2EB51A6F7EA7008CC3F3 /* View Controllers */, 117 | 67BA2EC71A6F7EF7008CC3F3 /* Supporting Files */, 118 | 67BA2EB31A6F7EA7008CC3F3 /* Images.xcassets */, 119 | ); 120 | path = SwiftChartsiOS; 121 | sourceTree = ""; 122 | }; 123 | 67BA2EB51A6F7EA7008CC3F3 /* View Controllers */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 67BA2EBA1A6F7EA7008CC3F3 /* SimpleLineChartViewController.swift */, 127 | 67BA2EB61A6F7EA7008CC3F3 /* AdvancedLineChartViewController.swift */, 128 | 67BA2EB81A6F7EA7008CC3F3 /* LabeledLineChartViewController.swift */, 129 | 67BA2EB71A6F7EA7008CC3F3 /* DynamicLineChartViewController.swift */, 130 | 67BA2EB91A6F7EA7008CC3F3 /* MultiAxisLineChartViewController.swift */, 131 | ); 132 | path = "View Controllers"; 133 | sourceTree = ""; 134 | }; 135 | 67BA2EC71A6F7EF7008CC3F3 /* Supporting Files */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | 67BA2EDA1A701BCA008CC3F3 /* Main.storyboard */, 139 | 67BA2EC91A701955008CC3F3 /* AppDelegate.swift */, 140 | 67BA2EAF1A6F7EA7008CC3F3 /* LaunchScreen.xib */, 141 | 67BA2EB41A6F7EA7008CC3F3 /* Info.plist */, 142 | ); 143 | name = "Supporting Files"; 144 | sourceTree = ""; 145 | }; 146 | 67BA2ED61A701B25008CC3F3 /* SwiftCharts */ = { 147 | isa = PBXGroup; 148 | children = ( 149 | 67BA2ED71A701B25008CC3F3 /* LineChart.swift */, 150 | ); 151 | path = SwiftCharts; 152 | sourceTree = ""; 153 | }; 154 | 67E7569E1A6F78EB0078C523 /* View Controllers */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | 67E756A31A6F78EB0078C523 /* SimpleLineChartViewController.swift */, 158 | 67E7569F1A6F78EB0078C523 /* AdvancedLineChartViewController.swift */, 159 | 67E756A11A6F78EB0078C523 /* LabeledLineChartViewController.swift */, 160 | 67E756A01A6F78EB0078C523 /* DynamicLineChartViewController.swift */, 161 | 67E756A21A6F78EB0078C523 /* MultiAxisLineChartViewController.swift */, 162 | ); 163 | path = "View Controllers"; 164 | sourceTree = ""; 165 | }; 166 | /* End PBXGroup section */ 167 | 168 | /* Begin PBXNativeTarget section */ 169 | 677C594F1A6F473500110D1C /* SwiftChartsiOS */ = { 170 | isa = PBXNativeTarget; 171 | buildConfigurationList = 677C59711A6F473500110D1C /* Build configuration list for PBXNativeTarget "SwiftChartsiOS" */; 172 | buildPhases = ( 173 | 677C594C1A6F473500110D1C /* Sources */, 174 | 677C594D1A6F473500110D1C /* Frameworks */, 175 | 677C594E1A6F473500110D1C /* Resources */, 176 | ); 177 | buildRules = ( 178 | ); 179 | dependencies = ( 180 | ); 181 | name = SwiftChartsiOS; 182 | productName = SwiftCharts; 183 | productReference = 677C59501A6F473500110D1C /* SwiftChartsiOS.app */; 184 | productType = "com.apple.product-type.application"; 185 | }; 186 | 677C597A1A6F475200110D1C /* SwiftChartsMac */ = { 187 | isa = PBXNativeTarget; 188 | buildConfigurationList = 677C59941A6F475200110D1C /* Build configuration list for PBXNativeTarget "SwiftChartsMac" */; 189 | buildPhases = ( 190 | 677C59771A6F475200110D1C /* Sources */, 191 | 677C59781A6F475200110D1C /* Frameworks */, 192 | 677C59791A6F475200110D1C /* Resources */, 193 | ); 194 | buildRules = ( 195 | ); 196 | dependencies = ( 197 | ); 198 | name = SwiftChartsMac; 199 | productName = SwiftChartsMac; 200 | productReference = 677C597B1A6F475200110D1C /* SwiftChartsMac.app */; 201 | productType = "com.apple.product-type.application"; 202 | }; 203 | /* End PBXNativeTarget section */ 204 | 205 | /* Begin PBXProject section */ 206 | 677C59481A6F473500110D1C /* Project object */ = { 207 | isa = PBXProject; 208 | attributes = { 209 | LastUpgradeCheck = 0610; 210 | ORGANIZATIONNAME = KevinBrewster; 211 | TargetAttributes = { 212 | 677C594F1A6F473500110D1C = { 213 | CreatedOnToolsVersion = 6.1.1; 214 | }; 215 | 677C597A1A6F475200110D1C = { 216 | CreatedOnToolsVersion = 6.1.1; 217 | }; 218 | }; 219 | }; 220 | buildConfigurationList = 677C594B1A6F473500110D1C /* Build configuration list for PBXProject "SwiftCharts" */; 221 | compatibilityVersion = "Xcode 3.2"; 222 | developmentRegion = English; 223 | hasScannedForEncodings = 0; 224 | knownRegions = ( 225 | en, 226 | Base, 227 | ); 228 | mainGroup = 677C59471A6F473500110D1C; 229 | productRefGroup = 677C59511A6F473500110D1C /* Products */; 230 | projectDirPath = ""; 231 | projectRoot = ""; 232 | targets = ( 233 | 677C594F1A6F473500110D1C /* SwiftChartsiOS */, 234 | 677C597A1A6F475200110D1C /* SwiftChartsMac */, 235 | ); 236 | }; 237 | /* End PBXProject section */ 238 | 239 | /* Begin PBXResourcesBuildPhase section */ 240 | 677C594E1A6F473500110D1C /* Resources */ = { 241 | isa = PBXResourcesBuildPhase; 242 | buildActionMask = 2147483647; 243 | files = ( 244 | 67BA2EBE1A6F7EA7008CC3F3 /* Images.xcassets in Resources */, 245 | 67BA2EDC1A701BCA008CC3F3 /* Main.storyboard in Resources */, 246 | ); 247 | runOnlyForDeploymentPostprocessing = 0; 248 | }; 249 | 677C59791A6F475200110D1C /* Resources */ = { 250 | isa = PBXResourcesBuildPhase; 251 | buildActionMask = 2147483647; 252 | files = ( 253 | 67BA2EDD1A701BCA008CC3F3 /* Main.storyboard in Resources */, 254 | 67BA2ED51A701AE4008CC3F3 /* Main.storyboard in Resources */, 255 | 677C59841A6F475200110D1C /* Images.xcassets in Resources */, 256 | ); 257 | runOnlyForDeploymentPostprocessing = 0; 258 | }; 259 | /* End PBXResourcesBuildPhase section */ 260 | 261 | /* Begin PBXSourcesBuildPhase section */ 262 | 677C594C1A6F473500110D1C /* Sources */ = { 263 | isa = PBXSourcesBuildPhase; 264 | buildActionMask = 2147483647; 265 | files = ( 266 | 67BA2EC11A6F7EA7008CC3F3 /* DynamicLineChartViewController.swift in Sources */, 267 | 67BA2EC41A6F7EA7008CC3F3 /* SimpleLineChartViewController.swift in Sources */, 268 | 67BA2EC21A6F7EA7008CC3F3 /* LabeledLineChartViewController.swift in Sources */, 269 | 67BA2ECA1A701955008CC3F3 /* AppDelegate.swift in Sources */, 270 | 67BA2ED81A701B25008CC3F3 /* LineChart.swift in Sources */, 271 | 67BA2EC01A6F7EA7008CC3F3 /* AdvancedLineChartViewController.swift in Sources */, 272 | 67BA2EC31A6F7EA7008CC3F3 /* MultiAxisLineChartViewController.swift in Sources */, 273 | ); 274 | runOnlyForDeploymentPostprocessing = 0; 275 | }; 276 | 677C59771A6F475200110D1C /* Sources */ = { 277 | isa = PBXSourcesBuildPhase; 278 | buildActionMask = 2147483647; 279 | files = ( 280 | 67E756A61A6F78EB0078C523 /* LabeledLineChartViewController.swift in Sources */, 281 | 67E756A71A6F78EB0078C523 /* MultiAxisLineChartViewController.swift in Sources */, 282 | 67E756A51A6F78EB0078C523 /* DynamicLineChartViewController.swift in Sources */, 283 | 67BA2ECC1A70195F008CC3F3 /* AppDelegate.swift in Sources */, 284 | 67BA2ED91A701B25008CC3F3 /* LineChart.swift in Sources */, 285 | 67E756A41A6F78EB0078C523 /* AdvancedLineChartViewController.swift in Sources */, 286 | 67E756A81A6F78EB0078C523 /* SimpleLineChartViewController.swift in Sources */, 287 | ); 288 | runOnlyForDeploymentPostprocessing = 0; 289 | }; 290 | /* End PBXSourcesBuildPhase section */ 291 | 292 | /* Begin PBXVariantGroup section */ 293 | 67BA2EAF1A6F7EA7008CC3F3 /* LaunchScreen.xib */ = { 294 | isa = PBXVariantGroup; 295 | children = ( 296 | 67BA2EB01A6F7EA7008CC3F3 /* Base */, 297 | ); 298 | name = LaunchScreen.xib; 299 | sourceTree = ""; 300 | }; 301 | 67BA2ED21A701AE4008CC3F3 /* Main.storyboard */ = { 302 | isa = PBXVariantGroup; 303 | children = ( 304 | 67BA2ED31A701AE4008CC3F3 /* Base */, 305 | ); 306 | name = Main.storyboard; 307 | sourceTree = ""; 308 | }; 309 | 67BA2EDA1A701BCA008CC3F3 /* Main.storyboard */ = { 310 | isa = PBXVariantGroup; 311 | children = ( 312 | 67BA2EDB1A701BCA008CC3F3 /* Base */, 313 | ); 314 | name = Main.storyboard; 315 | sourceTree = ""; 316 | }; 317 | /* End PBXVariantGroup section */ 318 | 319 | /* Begin XCBuildConfiguration section */ 320 | 677C596F1A6F473500110D1C /* Debug */ = { 321 | isa = XCBuildConfiguration; 322 | buildSettings = { 323 | ALWAYS_SEARCH_USER_PATHS = NO; 324 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 325 | CLANG_CXX_LIBRARY = "libc++"; 326 | CLANG_ENABLE_MODULES = YES; 327 | CLANG_ENABLE_OBJC_ARC = YES; 328 | CLANG_WARN_BOOL_CONVERSION = YES; 329 | CLANG_WARN_CONSTANT_CONVERSION = YES; 330 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 331 | CLANG_WARN_EMPTY_BODY = YES; 332 | CLANG_WARN_ENUM_CONVERSION = YES; 333 | CLANG_WARN_INT_CONVERSION = YES; 334 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 335 | CLANG_WARN_UNREACHABLE_CODE = YES; 336 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 337 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 338 | COPY_PHASE_STRIP = NO; 339 | ENABLE_STRICT_OBJC_MSGSEND = YES; 340 | GCC_C_LANGUAGE_STANDARD = gnu99; 341 | GCC_DYNAMIC_NO_PIC = NO; 342 | GCC_OPTIMIZATION_LEVEL = 0; 343 | GCC_PREPROCESSOR_DEFINITIONS = ( 344 | "DEBUG=1", 345 | "$(inherited)", 346 | ); 347 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 348 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 349 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 350 | GCC_WARN_UNDECLARED_SELECTOR = YES; 351 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 352 | GCC_WARN_UNUSED_FUNCTION = YES; 353 | GCC_WARN_UNUSED_VARIABLE = YES; 354 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 355 | MTL_ENABLE_DEBUG_INFO = YES; 356 | ONLY_ACTIVE_ARCH = YES; 357 | SDKROOT = iphoneos; 358 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 359 | TARGETED_DEVICE_FAMILY = 2; 360 | }; 361 | name = Debug; 362 | }; 363 | 677C59701A6F473500110D1C /* Release */ = { 364 | isa = XCBuildConfiguration; 365 | buildSettings = { 366 | ALWAYS_SEARCH_USER_PATHS = NO; 367 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 368 | CLANG_CXX_LIBRARY = "libc++"; 369 | CLANG_ENABLE_MODULES = YES; 370 | CLANG_ENABLE_OBJC_ARC = YES; 371 | CLANG_WARN_BOOL_CONVERSION = YES; 372 | CLANG_WARN_CONSTANT_CONVERSION = YES; 373 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 374 | CLANG_WARN_EMPTY_BODY = YES; 375 | CLANG_WARN_ENUM_CONVERSION = YES; 376 | CLANG_WARN_INT_CONVERSION = YES; 377 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 378 | CLANG_WARN_UNREACHABLE_CODE = YES; 379 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 380 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 381 | COPY_PHASE_STRIP = YES; 382 | ENABLE_NS_ASSERTIONS = NO; 383 | ENABLE_STRICT_OBJC_MSGSEND = YES; 384 | GCC_C_LANGUAGE_STANDARD = gnu99; 385 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 386 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 387 | GCC_WARN_UNDECLARED_SELECTOR = YES; 388 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 389 | GCC_WARN_UNUSED_FUNCTION = YES; 390 | GCC_WARN_UNUSED_VARIABLE = YES; 391 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 392 | MTL_ENABLE_DEBUG_INFO = NO; 393 | SDKROOT = iphoneos; 394 | TARGETED_DEVICE_FAMILY = 2; 395 | VALIDATE_PRODUCT = YES; 396 | }; 397 | name = Release; 398 | }; 399 | 677C59721A6F473500110D1C /* Debug */ = { 400 | isa = XCBuildConfiguration; 401 | buildSettings = { 402 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 403 | INFOPLIST_FILE = "$(SRCROOT)/SwiftChartsiOS/Info.plist"; 404 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 405 | PRODUCT_NAME = "$(TARGET_NAME)"; 406 | }; 407 | name = Debug; 408 | }; 409 | 677C59731A6F473500110D1C /* Release */ = { 410 | isa = XCBuildConfiguration; 411 | buildSettings = { 412 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 413 | INFOPLIST_FILE = "$(SRCROOT)/SwiftChartsiOS/Info.plist"; 414 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 415 | PRODUCT_NAME = "$(TARGET_NAME)"; 416 | }; 417 | name = Release; 418 | }; 419 | 677C59951A6F475200110D1C /* Debug */ = { 420 | isa = XCBuildConfiguration; 421 | buildSettings = { 422 | ARCHS = "$(ARCHS_STANDARD)"; 423 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 424 | CODE_SIGN_IDENTITY = "-"; 425 | COMBINE_HIDPI_IMAGES = YES; 426 | GCC_PREPROCESSOR_DEFINITIONS = ( 427 | "DEBUG=1", 428 | "$(inherited)", 429 | ); 430 | INFOPLIST_FILE = SwiftChartsMac/Info.plist; 431 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 432 | MACOSX_DEPLOYMENT_TARGET = 10.10; 433 | PRODUCT_NAME = "$(TARGET_NAME)"; 434 | SDKROOT = macosx; 435 | }; 436 | name = Debug; 437 | }; 438 | 677C59961A6F475200110D1C /* Release */ = { 439 | isa = XCBuildConfiguration; 440 | buildSettings = { 441 | ARCHS = "$(ARCHS_STANDARD)"; 442 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 443 | CODE_SIGN_IDENTITY = "-"; 444 | COMBINE_HIDPI_IMAGES = YES; 445 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 446 | INFOPLIST_FILE = SwiftChartsMac/Info.plist; 447 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 448 | MACOSX_DEPLOYMENT_TARGET = 10.10; 449 | PRODUCT_NAME = "$(TARGET_NAME)"; 450 | SDKROOT = macosx; 451 | }; 452 | name = Release; 453 | }; 454 | /* End XCBuildConfiguration section */ 455 | 456 | /* Begin XCConfigurationList section */ 457 | 677C594B1A6F473500110D1C /* Build configuration list for PBXProject "SwiftCharts" */ = { 458 | isa = XCConfigurationList; 459 | buildConfigurations = ( 460 | 677C596F1A6F473500110D1C /* Debug */, 461 | 677C59701A6F473500110D1C /* Release */, 462 | ); 463 | defaultConfigurationIsVisible = 0; 464 | defaultConfigurationName = Release; 465 | }; 466 | 677C59711A6F473500110D1C /* Build configuration list for PBXNativeTarget "SwiftChartsiOS" */ = { 467 | isa = XCConfigurationList; 468 | buildConfigurations = ( 469 | 677C59721A6F473500110D1C /* Debug */, 470 | 677C59731A6F473500110D1C /* Release */, 471 | ); 472 | defaultConfigurationIsVisible = 0; 473 | defaultConfigurationName = Release; 474 | }; 475 | 677C59941A6F475200110D1C /* Build configuration list for PBXNativeTarget "SwiftChartsMac" */ = { 476 | isa = XCConfigurationList; 477 | buildConfigurations = ( 478 | 677C59951A6F475200110D1C /* Debug */, 479 | 677C59961A6F475200110D1C /* Release */, 480 | ); 481 | defaultConfigurationIsVisible = 0; 482 | defaultConfigurationName = Release; 483 | }; 484 | /* End XCConfigurationList section */ 485 | }; 486 | rootObject = 677C59481A6F473500110D1C /* Project object */; 487 | } 488 | -------------------------------------------------------------------------------- /SwiftCharts.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftCharts/LineChart.swift: -------------------------------------------------------------------------------- 1 | /// 2 | // LineChart.swift 3 | // SensorTag 4 | // 5 | // Created by Kevin Brewster on 1/7/15. 6 | // Copyright (c) 2015 KevinBrewster. All rights reserved. 7 | // 8 | 9 | 10 | 11 | import QuartzCore 12 | #if os(iOS) 13 | 14 | // Mark: 15 | @objc protocol LineChartTooltip { 16 | var point: LineChart.Point? { get set } 17 | } 18 | 19 | 20 | import UIKit 21 | 22 | class LineChartView: UIView { 23 | override class func layerClass() -> AnyClass { return LineChart.self } 24 | var lineChart: LineChart { return layer as LineChart } 25 | var popoverController: UIPopoverController? 26 | var popoverPoint: LineChart.Point? 27 | 28 | 29 | required init(coder aDecoder: NSCoder) { 30 | super.init(coder: aDecoder) 31 | popoverController = UIPopoverController(contentViewController: SimpleLineChartTooltop(nibName: nil, bundle: nil)) 32 | 33 | lineChart.contentsScale = UIScreen.mainScreen().scale 34 | } 35 | 36 | override func layoutSubviews() { 37 | println("layoutSubviews") 38 | lineChart.frame = self.bounds 39 | } 40 | override func awakeFromNib() { 41 | println("Awake from nib") 42 | addGestureRecognizer( UIPanGestureRecognizer(target: self, action: Selector("pan:")) ) 43 | } 44 | func pan(recognizer: UIPanGestureRecognizer) { 45 | if popoverController != nil { 46 | if recognizer.state == UIGestureRecognizerState.Began { 47 | popoverController!.passthroughViews = [self] // add this view as a passthroughViews so pan gesture will continue working while popover is displayed 48 | 49 | } else if recognizer.state == UIGestureRecognizerState.Changed { 50 | var touchLocation = lineChart.convertPoint(recognizer.locationInView(self), toLayer: lineChart.datasets.first) 51 | if let point = lineChart.closestPointTo(touchLocation) { 52 | if popoverPoint == nil || popoverPoint! != point { 53 | if popoverPoint != nil { 54 | popoverPoint!.highlighted = false // un-highlight the previously highlighted point 55 | } 56 | popoverPoint = point 57 | if popoverController!.popoverVisible { 58 | popoverController!.dismissPopoverAnimated(false) 59 | } 60 | if let vc = popoverController!.contentViewController as? LineChartTooltip { 61 | // set the point of the popover contentViewController so it can update it's view 62 | vc.point = point 63 | } 64 | point.highlighted = true 65 | let rect = CGRect(origin: lineChart.convertPoint(point.position, fromLayer: lineChart.datasets.first), size: CGSize(width: 1.0, height: 1.0)) 66 | popoverController!.presentPopoverFromRect(rect, inView: self, permittedArrowDirections: UIPopoverArrowDirection.Any, animated: false) 67 | } 68 | } 69 | } else if recognizer.state == UIGestureRecognizerState.Ended { 70 | popoverController!.passthroughViews = [] 71 | if popoverController!.popoverVisible { 72 | popoverPoint!.highlighted = false // un-highlight the previously highlighted point 73 | popoverController!.dismissPopoverAnimated(true) 74 | } 75 | } 76 | } 77 | } 78 | } 79 | class SimpleLineChartTooltop : UIViewController, LineChartTooltip { 80 | // a very simple tooltip view controller to be presented by popover controller 81 | let lineLabel = UILabel(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 20.0)) 82 | let valueLabel = UILabel(frame: CGRect(x: 0.0, y: 20.0, width: 100.0, height: 20.0)) 83 | var point: LineChart.Point? { 84 | didSet { 85 | valueLabel.text = point == nil ? "" : point!.value.description 86 | lineLabel.text = point == nil || point!.dataset == nil ? "" : point!.dataset!.label 87 | } 88 | } 89 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { 90 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 91 | view.frame = CGRect(x: 0.0, y: 0.0, width: 100.0, height: 45.0) 92 | preferredContentSize = view.frame.size 93 | lineLabel.textAlignment = NSTextAlignment.Center 94 | view.addSubview(lineLabel) 95 | valueLabel.textAlignment = NSTextAlignment.Center 96 | view.addSubview(valueLabel) 97 | } 98 | required init(coder aDecoder: NSCoder) { 99 | fatalError("init(coder:) has not been implemented") 100 | } 101 | } 102 | 103 | #else 104 | import AppKit 105 | 106 | class LineChartView: NSView { 107 | // A simple NSView which "hosts" a LineChart as it's layer 108 | // It does a couple nice things on top of the core LineChart functionality: 109 | // 1) Auto-resizes the LineChart layer when view is resized 110 | // 2) Tracks mouse movement and displays a tooltip at the closest point 111 | 112 | let popover = NSPopover() 113 | var popoverPoint: LineChart.Point? 114 | var lineChart: LineChart { return layer as LineChart } 115 | var trackingArea: NSTrackingArea! 116 | 117 | required init?(coder: NSCoder) { 118 | super.init(coder: coder) 119 | self.layer = LineChart() as LineChart 120 | self.layer!.autoresizingMask = CAAutoresizingMask.LayerWidthSizable | CAAutoresizingMask.LayerHeightSizable 121 | self.layer!.needsDisplayOnBoundsChange = true 122 | self.wantsLayer = true 123 | 124 | popover.contentViewController = SimpleLineChartTooltop(nibName: nil, bundle: nil) 125 | updateTrackingAreas() 126 | } 127 | func layoutSubviews() { 128 | lineChart.frame = self.bounds 129 | } 130 | override func viewDidChangeBackingProperties() { 131 | println("viewDidChangeBackingProperties, new scale = \(window!.backingScaleFactor)") 132 | lineChart.contentsScale = window!.backingScaleFactor 133 | } 134 | 135 | // Mark: Mouse Tracking 136 | override func updateTrackingAreas() { 137 | if trackingArea != nil { 138 | removeTrackingArea(trackingArea) 139 | } 140 | // only track the mouse inside the actual graph, not when over the axis labels or legend e.g. 141 | let trackingRect = CGRect(x: lineChart.graphInsets.left, y: lineChart.graphInsets.bottom, width: bounds.width - lineChart.graphInsets.left - lineChart.graphInsets.right, height: bounds.height - lineChart.graphInsets.bottom - lineChart.graphInsets.top) 142 | trackingArea = NSTrackingArea(rect: trackingRect, options: NSTrackingAreaOptions.ActiveAlways | NSTrackingAreaOptions.MouseEnteredAndExited | NSTrackingAreaOptions.MouseMoved, owner: self, userInfo: nil) 143 | addTrackingArea(trackingArea) 144 | } 145 | override func mouseEntered(theEvent: NSEvent) { 146 | //println("mouseEntered") 147 | } 148 | override func mouseExited(theEvent: NSEvent) { 149 | if popoverPoint != nil { 150 | popoverPoint!.highlighted = false // un-highlight the previously highlighted point 151 | popoverPoint = nil 152 | } 153 | if popover.shown { 154 | popover.close() 155 | } 156 | } 157 | override func mouseMoved(theEvent: NSEvent) { 158 | //var mouseLocation = convertPoint(theEvent.locationInWindow, fromView: nil) 159 | var mouseLocation = lineChart.convertPoint(theEvent.locationInWindow, toLayer: lineChart.datasets.first) 160 | 161 | if let point = lineChart.closestPointTo(mouseLocation) { 162 | if popoverPoint == nil || popoverPoint! != point { 163 | if popoverPoint != nil { 164 | popoverPoint!.highlighted = false // un-highlight the previously highlighted point 165 | } 166 | popoverPoint = point 167 | 168 | popover.contentViewController!.representedObject = point 169 | point.highlighted = true 170 | popover.contentSize = NSSize(width: 100.0, height: 45.0) 171 | 172 | let rect = CGRect(origin: lineChart.convertPoint(point.position, fromLayer: lineChart.datasets.first), size: CGSize(width: 1.0, height: 1.0)) 173 | popover.showRelativeToRect(rect, ofView: self, preferredEdge: NSMaxYEdge) 174 | popover.contentViewController!.view.window!.ignoresMouseEvents = true // to prevent mouseExited from triggering when mouse over popover 175 | } 176 | } 177 | 178 | } 179 | } 180 | 181 | class SimpleLineChartTooltop : NSViewController { 182 | // a very simple tooltip view controller to be presented by popover controller 183 | let lineLabel = NSTextField(frame: CGRect(x: 0.0, y: 20.0, width: 100.0, height: 20.0)) 184 | let valueLabel = NSTextField(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 20.0)) 185 | /*var point: LineChart.Point? { 186 | didSet { 187 | valueLabel.stringValue = point == nil ? "" : point!.value.shortFormatted 188 | lineLabel.stringValue = point == nil || point!.line == nil ? "" : point!.line!.label 189 | } 190 | }*/ 191 | override init?(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { 192 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 193 | self.view = NSView(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 45.0)) 194 | 195 | for label in [lineLabel, valueLabel] { 196 | label.editable = false 197 | label.selectable = false 198 | label.bordered = false 199 | label.alignment = NSTextAlignment.CenterTextAlignment 200 | label.backgroundColor = NSColor.clearColor() 201 | view.addSubview(label) 202 | } 203 | } 204 | required init?(coder: NSCoder) { 205 | fatalError("init(coder:) has not been implemented") 206 | } 207 | override var representedObject: AnyObject? { 208 | didSet { 209 | if let point = representedObject as? LineChart.Point { 210 | valueLabel.stringValue = "\(point.value)" 211 | lineLabel.stringValue = point.dataset!.label 212 | } 213 | } 214 | } 215 | } 216 | 217 | #endif 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | class LineChart: CALayer { 226 | let defaultColors = [0x1f77b4, 0xff7f0e, 0x2ca02c, 0xd62728, 0x9467bd, 0x8c564b, 0xe377c2, 0x7f7f7f, 0xbcbd22, 0x17becf].map { CGColorCreateFromHex($0) } 227 | 228 | var datasets: [Dataset] = [Dataset]() { 229 | didSet { 230 | for dataset in oldValue { 231 | dataset.removeFromSuperlayer() 232 | dataset.legendElement.removeFromSuperlayer() 233 | } 234 | for dataset in datasets { 235 | if dataset.xAxis == nil { 236 | dataset.xAxis = xAxis 237 | } 238 | if dataset.yAxis == nil { 239 | dataset.yAxis = self.yAxes.first! 240 | } 241 | if !contains(yAxes, dataset.yAxis) { 242 | yAxes += [dataset.yAxis] 243 | } 244 | if dataset.strokeColor == nil { 245 | let colorIndex = (datasets.count - 1) % 5 246 | dataset.color = defaultColors[colorIndex] 247 | } 248 | dataset.delegate = self 249 | addSublayer(dataset) 250 | legend.addSublayer(dataset.legendElement) 251 | } 252 | updateXAxis() 253 | updateLegend() 254 | } 255 | } 256 | 257 | 258 | private(set) var xAxis: Axis = Axis(alignment: .Right, transform: CATransform3DMakeRotation(CGFloat(M_PI) / -2.0, 0.0, 0.0, 1.0)) 259 | var yAxes: [Axis] = [Axis(alignment: .Left)] { 260 | didSet { 261 | for axis in oldValue { 262 | axis.removeFromSuperlayer() 263 | } 264 | for axis in yAxes { 265 | axis.delegate = self 266 | addSublayer(axis) 267 | } 268 | } 269 | } 270 | var graphInsets: (left: CGFloat, top: CGFloat, right: CGFloat, bottom: CGFloat) = (0.0, 0.0, 0.0, 0.0) { 271 | didSet { 272 | //println("Graph insets did set") 273 | } 274 | } 275 | 276 | var title: CATextLayer = CATextLayer(autoScale: true) 277 | var legend = CALayer() 278 | 279 | var legendEnabled: Bool = false { 280 | didSet { 281 | if legendEnabled { 282 | graphInsets.top = 60.0 // todo: calculate this value 283 | addSublayer(legend) 284 | } else { 285 | graphInsets.top = 10.0 286 | legend.removeFromSuperlayer() 287 | } 288 | } 289 | } 290 | 291 | var animationDurations = CATransaction.animationDuration() // can't call it "animationDuration" or swift compile error - no idea why 292 | var animationTimingFunction = CATransaction.animationTimingFunction() 293 | 294 | /*override init!(layer: AnyObject!) { 295 | super.init(layer: layer) 296 | }*/ 297 | override init() { 298 | super.init() 299 | postInit() 300 | } 301 | required init(coder aDecoder: NSCoder) { 302 | super.init(coder: aDecoder) 303 | postInit() 304 | } 305 | override init!(layer: AnyObject!) { 306 | super.init(layer: layer) 307 | } 308 | func postInit() { 309 | geometryFlipped = true 310 | delegate = self 311 | legend.delegate = self 312 | 313 | xAxis.delegate = self 314 | addSublayer(xAxis) 315 | 316 | yAxes.first!.delegate = self 317 | addSublayer(yAxes.first!) 318 | legendEnabled = true 319 | } 320 | func updateXAxis() { 321 | // X-Axis 322 | let minXTickValue: CGFloat = 0.0 // x-axis always starts at zero 323 | let maxXTickValue = CGFloat( datasets.reduce(0, {$0 > $1.data.count ? $0 : $1.data.count }) - 1 ) 324 | xAxis.range = (minXTickValue, maxXTickValue, interval: 1.0) 325 | xAxis.size = CGSize(width: 40.0, height: 0.0) 326 | graphInsets.bottom = xAxis.size.width 327 | } 328 | func updateLegend() { 329 | //legend.sublayers = nil 330 | var lastLegendElementOrigin = CGPointZero 331 | for dataset in datasets { 332 | //legend.addSublayer(legendEl) 333 | dataset.legendElement.frame = CGRect(origin: lastLegendElementOrigin, size:dataset.legendElement.frame.size) 334 | lastLegendElementOrigin = CGPoint(x: lastLegendElementOrigin.x + dataset.legendElement.frame.width + 20.0, y: 0.0) 335 | } 336 | legend.bounds = CGRect(origin: CGPointZero, size: CGSize(width: lastLegendElementOrigin.x, height: 20.0)) 337 | } 338 | override func layoutSublayers() { 339 | if datasets.count == 0 { 340 | return 341 | } 342 | 343 | // 1) Estimate Y-axis widths so we know graph width 344 | var estimatedYAxisWidths = 50.0 * CGFloat(yAxes.count) 345 | 346 | // 2) Knowing graph width, figure out X-axis height 347 | let maxTickLabelWidth: CGFloat = xAxis.ticks.reduce(0.0, { max($0, $1.labelLayer.bounds.width) }) 348 | let tickIntervalWidth = (bounds.width - estimatedYAxisWidths) / CGFloat(xAxis.ticks.count) 349 | if maxTickLabelWidth > tickIntervalWidth { 350 | // we need to rotate x-axis tick labels so they are not overlapping 351 | xAxis.labelRotation = -acos(tickIntervalWidth * 0.9 / maxTickLabelWidth) 352 | } else { 353 | xAxis.labelRotation = 0.0 354 | } 355 | let maxTickLabelHeight: CGFloat = xAxis.ticks.reduce(0.0, { max($0, $1.labelLayer.frame.width) }) 356 | xAxis.size = CGSize(width: 12.0 + maxTickLabelHeight + 16.0, height: 0.0) 357 | graphInsets.bottom = xAxis.size.width 358 | graphInsets.right = (xAxis.ticks.last!.labelLayer.frame.height / 2.0) + 8.0 // half the label will stick out a bit on the right, so leave a little room 359 | 360 | 361 | // 3) Knowing x-axis height and title, we know graph height and can figure out a "nice" amount of y-axis ticks 362 | graphInsets.left = 0.0 363 | 364 | for yAxis in yAxes { 365 | let datasetsForAxis = datasets.filter { $0.yAxis == yAxis } 366 | 367 | // figure out how many Y-Axis tick marks look best and at what interval 368 | let minYValue = datasetsForAxis.reduce(CGFloat.max, { min($0, minElement($1.data)) }) 369 | let maxYValue = datasetsForAxis.reduce(CGFloat.min, { max($0, maxElement($1.data)) }) 370 | let yTickInterval = yAxis.optimalInterval(minYValue, max: maxYValue) 371 | 372 | var minYTickValue = minYValue - (yTickInterval / 4.0) // we want the bottom of graph to be at least quarter-step below minY 373 | minYTickValue = floor(minYTickValue / yTickInterval) * yTickInterval 374 | if minYValue > 0.0 && minYTickValue < 0.0 { 375 | minYTickValue = 0.0 // silly to show negative numbers when all numbers are positive 376 | } 377 | 378 | var maxYTickValue = maxYValue + (yTickInterval / 4.0) // we want the bottom of graph to be at least quarter-step below minY 379 | maxYTickValue = ceil(maxYTickValue / yTickInterval) * yTickInterval 380 | yAxis.range = (minYTickValue, maxYTickValue, yTickInterval) 381 | 382 | let maxTickWidth: CGFloat = yAxis.ticks.reduce(0.0, { max($0, $1.width) }) 383 | let axisWidth = maxTickWidth + 16.0 384 | yAxis.size = CGSize(width: axisWidth, height: bounds.height - graphInsets.top - graphInsets.bottom) 385 | 386 | if yAxis.alignment == .Left { 387 | graphInsets.left += yAxis.size.width 388 | } else { 389 | graphInsets.right += yAxis.size.width 390 | yAxis.position = CGPoint(x: bounds.width - graphInsets.left - graphInsets.right, y: 0.0) 391 | } 392 | } 393 | yAxes.first!.showGrid(bounds.width - graphInsets.left - graphInsets.right) 394 | 395 | 396 | // 4) Knowing the exact y-axes widths, we can layout the x-axis 397 | xAxis.size = CGSize(width: xAxis.bounds.height, height: bounds.width - graphInsets.left - graphInsets.right) 398 | xAxis.layoutSublayers() 399 | //xAxis.gridWidth = bounds.height - graphInsets.top - graphInsets.bottom 400 | //xAxis.showGrid(bounds.height - graphInsets.top - graphInsets.bottom) 401 | 402 | 403 | // 5) Configure layer so coordinate system starts in lower left with (0,0) pixel point at origin of graph 404 | sublayerTransform = CATransform3DMakeAffineTransform(CGAffineTransformMakeTranslation(graphInsets.left, graphInsets.bottom)) 405 | 406 | // 6) Update legend position 407 | legend.position = CGPoint(x: self.bounds.width - graphInsets.left - legend.bounds.width, y: self.bounds.height - graphInsets.bottom - 40.0) 408 | 409 | // 7) Refresh the individual datasets 410 | for dataset in datasets { 411 | dataset.layoutSublayers() 412 | } 413 | } 414 | 415 | // Provide smooth animation for both path and position 416 | override func actionForLayer(layer: CALayer!, forKey event: String!) -> CAAction! { 417 | if event == "path" || event == "position" { 418 | let animation = CABasicAnimation(keyPath: event) 419 | animation.duration = animationDurations 420 | animation.timingFunction = animationTimingFunction 421 | return animation 422 | } 423 | return nil 424 | } 425 | #if os(OSX) 426 | override func layer(layer: CALayer, shouldInheritContentsScale newScale: CGFloat, fromWindow window: NSWindow) -> Bool { 427 | return true 428 | } 429 | #endif 430 | } 431 | 432 | 433 | 434 | 435 | 436 | // Mark: Other Classes 437 | 438 | extension LineChart { 439 | enum Alignment { case Left, Right} 440 | 441 | class Axis : CALayer { 442 | var label: String? 443 | var ticks: [Tick] = [Tick]() 444 | var size: CGSize! 445 | var gridWidth: CGFloat? 446 | var alignment: Alignment 447 | var axisLine = CAShapeLayer() 448 | var labels: [String]? 449 | var labelRotation: CGFloat = 0.0 { 450 | didSet { 451 | var correctedRotation = labelRotation 452 | if let axisRotation = valueForKeyPath("transform.rotation.z") as? CGFloat { 453 | // if the axis has been rotated, we need to rotate the text so it's right-side up 454 | correctedRotation -= axisRotation 455 | } 456 | for tick in ticks { 457 | var transform = CATransform3DIdentity 458 | tick.labelLayer.transform = CATransform3DRotate(transform, correctedRotation, 0.0, 0.0, 1.0) 459 | tick.updateLabelPosition() 460 | } 461 | } 462 | } 463 | 464 | var range: (min: CGFloat, max: CGFloat, interval: CGFloat?)! { 465 | didSet { 466 | if range.interval == nil { 467 | range.interval = 1.0 468 | } 469 | for tick in ticks { 470 | tick.removeFromSuperlayer() 471 | } 472 | let extra = range.interval! / 2.0 // // the "extra" craziness is to make sure the final condition evaluates as true if float values are very, very close but not equal (i.e. sometimes 1.0 != 1.0) 473 | var newTicks = [Tick]() 474 | var index = 0 475 | for var value = range.min; value <= range.max + extra; value += range.interval!, index++ { 476 | let tick = index < ticks.count ? ticks[index] : Tick(value: value, alignment: alignment, major: true) 477 | tick.delegate = delegate 478 | tick.value = value 479 | 480 | if labels != nil && index < labels!.count { 481 | tick.label = labels![index] 482 | } 483 | 484 | addSublayer(tick) 485 | newTicks += [tick] 486 | 487 | 488 | } 489 | ticks = newTicks 490 | } 491 | } 492 | override weak var delegate: AnyObject! { 493 | didSet { axisLine.delegate = delegate } 494 | } 495 | 496 | init(alignment: Alignment = .Left, transform: CATransform3D = CATransform3DIdentity) { 497 | self.alignment = alignment 498 | super.init() 499 | self.transform = transform 500 | 501 | 502 | axisLine.strokeColor = CGColorCreateCopyWithAlpha(CGColorCreateFromHex(0x000000), 0.4) 503 | addSublayer(axisLine) 504 | } 505 | required init(coder aDecoder: NSCoder) { 506 | fatalError("init(coder:) has not been implemented") 507 | } 508 | override init!(layer: AnyObject!) { 509 | self.alignment = .Left 510 | super.init(layer: layer) 511 | } 512 | 513 | override func layoutSublayers() { 514 | if size == nil { 515 | return 516 | } 517 | axisLine.path = CGPath.Dataset(CGPoint(x: 0, y: 0), end: CGPoint(x: 0, y: size.height)) 518 | 519 | let scaleFactor = size.height / (range.max - range.min) 520 | for tick in ticks { 521 | let y = (tick.value - range.min) * scaleFactor 522 | tick.position = CGPoint(x: 0.0, y: y) 523 | } 524 | } 525 | 526 | func optimalInterval(min: CGFloat, max: CGFloat) -> CGFloat { 527 | let maxInterval: CGFloat = 15.0 // todo: calc based on label font height 528 | let minInterval = (max - min) / 0.9 / maxInterval // if we used the max amount of steps, each step would have represent at least this many units 529 | // divide by 0.9 because we want all the data points to only take up ~90% of the available vertical space. the leftover is to provide some extra space below the min point and above the max point 530 | var magnitude = pow(10.0, round( log10(minInterval) )) 531 | 532 | // in order for the ticks to be "pretty" or intuitive, make them in multiples of either (100, 10, 1, .1, etc) or (50, 5, .5, .05) or (20, 2, .2, .02, etc) 533 | var stepBase: CGFloat = 0.0 534 | if minInterval <= 1.0 * magnitude { 535 | stepBase = 1.0 536 | } else if minInterval <= 2.0 * magnitude { 537 | stepBase = 2.0 538 | } else { 539 | stepBase = 5.0 540 | } 541 | return stepBase * magnitude 542 | } 543 | func showGrid(width: CGFloat) { 544 | for tick in ticks { 545 | if tick.major { 546 | let xStart = alignment == .Left ? -6.0 : 6.0 547 | let xEnd = alignment == .Left ? width : -width 548 | tick.lineLayer.path = CGPath.Dataset(CGPoint(x: xStart, y: 0), end: CGPoint(x: xEnd, y: 0)) 549 | } 550 | } 551 | } 552 | } 553 | class Tick : CALayer { 554 | var major = true 555 | var alignment: Alignment! 556 | var labelLayer = CATextLayer(autoScale: true) 557 | var lineLayer = CAShapeLayer() 558 | var width: CGFloat { return 12.0 + labelLayer.frame.size.width } 559 | var value: CGFloat! { 560 | didSet { label = value.shortFormatted } 561 | } 562 | let labelSpacing: CGFloat = 12.0 563 | 564 | var label: String { 565 | get { return labelLayer.string as String } 566 | set { 567 | labelLayer.string = newValue 568 | labelLayer.sizeToFit() 569 | updateLabelPosition() 570 | 571 | } 572 | } 573 | override weak var delegate: AnyObject! { 574 | didSet { 575 | labelLayer.delegate = delegate // so it can get contentsScale 576 | } 577 | } 578 | 579 | func updateLabelPosition() { 580 | if alignment == .Left { 581 | labelLayer.position = CGPoint(x: -labelSpacing - (labelLayer.frame.width / 2.0), y: 0.0) 582 | labelLayer.alignmentMode = kCAAlignmentRight 583 | } else { 584 | labelLayer.position = CGPoint(x: labelSpacing + (labelLayer.frame.width / 2.0), y: 0.0) 585 | labelLayer.alignmentMode = kCAAlignmentLeft 586 | } 587 | } 588 | init(value: CGFloat, alignment: Alignment, major: Bool) { 589 | self.value = value 590 | self.alignment = alignment 591 | self.major = major 592 | super.init() 593 | 594 | // Tick / Grid 595 | var xStart: CGFloat 596 | var xEnd: CGFloat 597 | 598 | if major { 599 | xStart = alignment == .Left ? -6.0 : 6.0 600 | xEnd = alignment == .Left ? 6.0 : -6.0 601 | } else { 602 | xStart = -3.0 603 | xEnd = 3.0 604 | } 605 | lineLayer.path = CGPath.Dataset(CGPoint(x: xStart, y: 0), end: CGPoint(x: xEnd, y: 0)) 606 | lineLayer.strokeColor = CGColorCreateCopyWithAlpha(CGColorCreateFromHex(0x000000), 0.2) 607 | addSublayer(lineLayer) 608 | 609 | // Label 610 | labelLayer.fontSize = 12.0 611 | labelLayer.foregroundColor = CGColorCreateFromHex(0x444444) 612 | addSublayer(labelLayer) 613 | } 614 | required init(coder aDecoder: NSCoder) { 615 | fatalError("init(coder:) has not been implemented") 616 | } 617 | override init!(layer: AnyObject!) { 618 | super.init(layer: layer) 619 | } 620 | } 621 | enum Curve { 622 | case Bezier(CGFloat) 623 | } 624 | class Dataset : CAShapeLayer { 625 | 626 | var xAxis: Axis! 627 | var yAxis: Axis! 628 | var label = "" 629 | var data: [CGFloat] = [CGFloat]() { 630 | didSet { 631 | updatePoints() 632 | 633 | if let delegate = delegate as? LineChart { 634 | delegate.updateXAxis() 635 | delegate.setNeedsLayout() 636 | } 637 | } 638 | } 639 | private let fillLayer = CAShapeLayer() 640 | 641 | var curve: Curve? 642 | let defaultPoint = CAShapeLayer() 643 | var points = [Point]() 644 | 645 | override weak var delegate: AnyObject! { 646 | didSet { 647 | legendLabel.delegate = delegate 648 | fillLayer.delegate = delegate 649 | for point in points { point.delegate = delegate } 650 | } 651 | } 652 | lazy var legendMarker: CAShapeLayer = { 653 | let legendMarker = CAShapeLayer() 654 | legendMarker.path = CGPath.Rect(CGRect(x: 0.0, y: 0.0, width: 20.0, height: 15.0)) 655 | legendMarker.fillColor = self.strokeColor 656 | return legendMarker 657 | }() 658 | lazy var legendLabel: CATextLayer = { 659 | let legendLabel = CATextLayer(autoScale: true) 660 | //legendLabel.contentsScale = UIScreen.mainScreen().scale 661 | legendLabel.fontSize = 12.0 662 | legendLabel.string = self.label 663 | legendLabel.foregroundColor = CGColorCreateFromHex(0x000000) 664 | legendLabel.position = CGPoint(x: 14.0, y:0.0) 665 | legendLabel.alignmentMode = kCAAlignmentLeft 666 | legendLabel.sizeToFit() 667 | legendLabel.frame = CGRect(origin: CGPoint(x: 28.0, y: 0.0), size: legendLabel.frame.size) 668 | return legendLabel 669 | }() 670 | lazy var legendElement: CALayer = { 671 | let legendEl = CALayer() 672 | legendEl.addSublayer(self.legendMarker) 673 | legendEl.frame = CGRect(origin: CGPointZero, size: CGSize(width: self.legendLabel.frame.origin.x + self.legendLabel.frame.width, height: 15.0)) 674 | legendEl.addSublayer(self.legendLabel) 675 | return legendEl 676 | }() 677 | override var path: CGPath! { 678 | didSet { 679 | let fillPath = CGPathCreateMutable() 680 | 681 | // We need to use this special ordering of points so the animated transition/morphing looks correct 682 | let lastPoint = CGPathGetCurrentPoint(path) 683 | let start = oldValue != nil ? CGPathGetCurrentPoint(oldValue) : lastPoint 684 | CGPathMoveToPoint(fillPath, nil, start.x, 0) 685 | CGPathAddLineToPoint(fillPath, nil, 0, 0) 686 | CGPathAddLineToPoint(fillPath, nil, points.first!.position.x, points.first!.position.y) 687 | CGPathAddPath(fillPath, nil, path) 688 | CGPathAddLineToPoint(fillPath, nil, lastPoint.x, lastPoint.y) // duplicate point important 689 | CGPathAddLineToPoint(fillPath, nil, lastPoint.x, 0) 690 | CGPathAddLineToPoint(fillPath, nil, start.x, 0) 691 | CGPathCloseSubpath(fillPath) 692 | fillLayer.path = fillPath 693 | } 694 | } 695 | override var fillColor: CGColorRef! { 696 | get { return nil } 697 | set { 698 | super.fillColor = nil 699 | fillLayer.fillColor = newValue 700 | } 701 | } 702 | 703 | convenience init(label: String, data: [CGFloat], yAxis: Axis) { 704 | self.init(label: label, data: data) 705 | self.yAxis = yAxis 706 | } 707 | init(label: String, data: [CGFloat]) { 708 | self.label = label 709 | self.data = data 710 | 711 | 712 | defaultPoint.path = CGPath.Circle(10.0) 713 | defaultPoint.strokeColor = CGColorCreateFromHex(0xFFFFFF) 714 | 715 | super.init() 716 | updatePoints() 717 | 718 | lineWidth = 2.0 719 | 720 | defaultPoint.addObserver(self, forKeyPath: "strokeColor", options: NSKeyValueObservingOptions.allZeros, context: nil) 721 | defaultPoint.addObserver(self, forKeyPath: "fillColor", options: NSKeyValueObservingOptions.allZeros, context: nil) 722 | defaultPoint.addObserver(self, forKeyPath: "path", options: NSKeyValueObservingOptions.allZeros, context: nil) 723 | 724 | addSublayer(fillLayer) 725 | //updatePoints(); 726 | } 727 | override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer) { 728 | for point in points { 729 | point.setValue(defaultPoint.valueForKeyPath(keyPath), forKeyPath: keyPath) 730 | } 731 | } 732 | required init(coder aDecoder: NSCoder) { 733 | fatalError("init(coder:) has not been implemented") 734 | } 735 | override init!(layer: AnyObject!) { 736 | super.init(layer: layer) 737 | } 738 | func updatePoints() { 739 | for point in points { 740 | point.removeFromSuperlayer() 741 | } 742 | var newPoints = [Point]() 743 | for var i = 0; i < data.count; i++ { 744 | let point = i < points.count ? points[i] : Point(layer: defaultPoint) 745 | point.dataset = self 746 | point.delegate = delegate 747 | point.value = data[i] 748 | point.highlighted = false 749 | addSublayer(point) 750 | newPoints += [point] 751 | } 752 | points = newPoints 753 | } 754 | var color: CGColorRef! { 755 | get { return strokeColor } 756 | set { 757 | strokeColor = newValue 758 | fillColor = CGColorCreateCopyWithAlpha(newValue, 0.3) 759 | defaultPoint.fillColor = newValue 760 | legendMarker.fillColor = newValue 761 | } 762 | } 763 | override func layoutSublayers() { 764 | var x: CGFloat = 0 765 | let yScaleFactor = yAxis.size.height / (yAxis.range.max - yAxis.range.min) 766 | let xIntervalWidth = xAxis.size.height / (xAxis.range.max - xAxis.range.min) 767 | 768 | for point in points { 769 | let y = (point.value - yAxis.range.min) * yScaleFactor 770 | point.position = CGPoint(x: x, y: y) 771 | x += xIntervalWidth 772 | } 773 | let positions = points.map { $0.position } 774 | 775 | if curve != nil { 776 | switch curve! { 777 | case .Bezier(let tension): 778 | path = CGPath.SplineCurve(positions, tension: tension) 779 | } 780 | } else { 781 | path = CGPath.Polyline(positions) 782 | } 783 | } 784 | } 785 | class Point : CAShapeLayer { 786 | var value: CGFloat! 787 | weak var dataset: Dataset? 788 | 789 | override init!(layer: AnyObject!) { 790 | super.init(layer: layer) 791 | path = layer.path 792 | strokeColor = layer.strokeColor 793 | fillColor = layer.fillColor 794 | } 795 | required init(coder aDecoder: NSCoder) { 796 | fatalError("init(coder:) has not been implemented") 797 | } 798 | var highlighted: Bool = false { 799 | didSet { 800 | if highlighted { 801 | transform = CATransform3DMakeAffineTransform( CGAffineTransformMakeScale(2.0, 2.0) ) 802 | } else { 803 | transform = CATransform3DMakeAffineTransform( CGAffineTransformMakeScale(1.0, 1.0) ) 804 | } 805 | } 806 | } 807 | } 808 | } 809 | 810 | 811 | // Mark: User Interaction 812 | 813 | extension LineChart { 814 | func closestPointTo(refPoint: CGPoint) -> Point? { 815 | var min: (point: Point, xDist: CGFloat, yDist: CGFloat)? 816 | for dataset in datasets { 817 | for point in dataset.points { 818 | let xDist = abs(refPoint.x - point.position.x) 819 | let yDist = abs(refPoint.y - point.position.y) 820 | 821 | if min == nil || xDist < min!.xDist || (xDist == min!.xDist && yDist < min!.yDist) { 822 | min = (point, xDist, yDist) 823 | } 824 | } 825 | } 826 | return (min == nil) ? nil : min!.point 827 | } 828 | } 829 | 830 | 831 | // Mark: Helpers 832 | 833 | func CGColorCreateFromHex(rgb: UInt) -> CGColor { 834 | return CGColorCreate(CGColorSpaceCreateDeviceRGB(), [CGFloat((rgb & 0xFF0000) >> 16) / 255.0, CGFloat((rgb & 0x00FF00) >> 8) / 255.0, CGFloat(rgb & 0x0000FF) / 255.0, 1.0]) 835 | } 836 | extension CGFloat { 837 | var shortFormatted: String { 838 | if self == 0.0 { 839 | return "0" 840 | } 841 | let exponent = floor(log10(self)) 842 | let formatter = NSNumberFormatter() 843 | 844 | let prefixes = [3:"k", 6:"M"] 845 | 846 | if exponent > 3 && exponent < 6 && self % 100 == 0 { 847 | formatter.maximumSignificantDigits = 3 848 | return formatter.stringFromNumber(self / 1000)! + "k" 849 | } else if exponent >= 6 && exponent < 9 && self % 100000 == 0 { 850 | formatter.maximumSignificantDigits = 3 851 | return formatter.stringFromNumber(self / 1000000)! + "M" 852 | } 853 | if exponent > 4 || exponent < -4 { 854 | formatter.numberStyle = NSNumberFormatterStyle.ScientificStyle 855 | formatter.maximumSignificantDigits = 5 856 | } else { 857 | formatter.numberStyle = NSNumberFormatterStyle.DecimalStyle 858 | formatter.maximumSignificantDigits = 5 859 | } 860 | return formatter.stringFromNumber(self)! 861 | } 862 | } 863 | 864 | extension CGPath { 865 | class func Dataset(start: CGPoint, end: CGPoint) -> CGPath { 866 | var path = CGPathCreateMutable() 867 | CGPathMoveToPoint(path, nil, start.x, start.y) 868 | CGPathAddLineToPoint(path, nil, end.x, end.y) 869 | 870 | return CGPathCreateCopy(path) 871 | } 872 | class func Polyline(points:[CGPoint]) -> CGPath { 873 | var path = CGPathCreateMutable() 874 | for point in points { 875 | if point == points.first { 876 | CGPathMoveToPoint(path, nil, point.x, point.y) 877 | } else { 878 | CGPathAddLineToPoint(path, nil, point.x, point.y) 879 | } 880 | } 881 | return path 882 | } 883 | class func Circle(radius: CGFloat) -> CGPath { 884 | return CGPathCreateWithEllipseInRect(CGRect(x: (radius / -2.0), y: (radius / -2.0), width: radius, height: radius), nil) 885 | } 886 | class func Rect(rect: CGRect) -> CGPath { 887 | return CGPathCreateWithRect(rect, nil) 888 | } 889 | class func SplineCurve(points: [CGPoint], tension: CGFloat = 0.3, minY: CGFloat = CGFloat.min, maxY: CGFloat = CGFloat.max) -> CGPath { 890 | func controlPoints(t: CGPoint, i: CGPoint, e: CGPoint, s: CGFloat) -> (inner: CGPoint, outer: CGPoint) { 891 | // adapted from chart.js helper library 892 | // calculates the control points for bezier curve for a dataset 893 | var n = sqrt(pow(i.x - t.x, 2) + pow(i.y - t.y, 2)); 894 | var o = sqrt(pow(e.x - i.x, 2) + pow(e.y - i.y, 2)) 895 | var a = s * n / (n + o) 896 | var h = s * o / (n + o) 897 | var inner = CGPoint(x: i.x - a * (e.x - t.x), y: i.y - a * (e.y - t.y)) 898 | var outer = CGPoint(x: i.x + h * (e.x - t.x), y: i.y + h * (e.y - t.y)) 899 | return (inner, outer) 900 | } 901 | 902 | var path = CGPathCreateMutable() 903 | var prevControlPoints: (inner: CGPoint, outer: CGPoint)? 904 | 905 | for var i = 0; i < points.count; i++ { 906 | let point = points[i] 907 | 908 | // bezier curve control point calculations 909 | let pTension: CGFloat = i > 0 && i < points.count - 1 ? tension : 0.0 910 | let prevPoint = i > 0 ? points[i-1] : CGPointZero 911 | let nextPoint = i < points.count - 1 ? points[i+1] : CGPointZero 912 | var controlPoints = controlPoints(prevPoint, point, nextPoint, pTension) 913 | 914 | // if it doesn't make sense for data to ever go below or above a certain value, then we can cap it off 915 | controlPoints = ( CGPoint(x: controlPoints.inner.x, y: max(min(controlPoints.inner.y, maxY), minY)), 916 | CGPoint(x: controlPoints.outer.x, y: max(min(controlPoints.outer.y, maxY), minY)) ) 917 | 918 | if i == 0 { 919 | CGPathMoveToPoint(path, nil, point.x, point.y) 920 | } else { 921 | CGPathAddCurveToPoint(path, nil, prevControlPoints!.outer.x, prevControlPoints!.outer.y, controlPoints.inner.x, controlPoints.inner.y, point.x, point.y) 922 | } 923 | prevControlPoints = controlPoints 924 | } 925 | return path 926 | 927 | 928 | } 929 | 930 | } 931 | extension CGAffineTransform { 932 | init(verticalFlipWithHeight height: CGFloat) { 933 | var t = CGAffineTransformMakeScale(1.0, -1.0) 934 | //CGAffineTransformTranslate(t, 0.0, -height) 935 | self.init(a: t.a, b:t.b, c:t.c, d:t.d, tx:t.tx, ty:t.ty) 936 | } 937 | } 938 | extension CATextLayer { 939 | convenience init(autoScale: Bool) { 940 | self.init() 941 | if autoScale { 942 | #if os(iOS) 943 | contentsScale = UIScreen.mainScreen().scale 944 | #else 945 | contentsScale = NSScreen.mainScreen()!.backingScaleFactor 946 | #endif 947 | } 948 | } 949 | // Helper function - It's nice to have access to an NSAttributedString because you can use the attributedString.size property to determine the correct frame for the layer 950 | var attributedString : NSAttributedString { 951 | if let attString = string as? NSAttributedString { 952 | return attString 953 | } else if let string = string as? NSString { 954 | var layerFont: CTFontRef? 955 | 956 | if let fontName = font as? NSString { 957 | //layerFont = UIFont(name: fontName, size: fontSize) 958 | layerFont = CTFontCreateWithName(fontName, fontSize, nil); 959 | } else { 960 | let ftypeid = CFGetTypeID(font) 961 | if ftypeid == CTFontGetTypeID() { 962 | let fontName = CTFontCopyPostScriptName(font as CTFont) 963 | //layerFont = UIFont(name: fontName, size: fontSize) 964 | layerFont = CTFontCreateWithName(fontName, fontSize, nil); 965 | } else if ftypeid == CGFontGetTypeID() { 966 | let fontName = CGFontCopyPostScriptName(font as CGFont) 967 | //layerFont = UIFont(name: fontName, size: fontSize) 968 | layerFont = CTFontCreateWithName(fontName, fontSize, nil); 969 | } 970 | } 971 | if layerFont == nil { 972 | //layerFont = UIFont.systemFontOfSize(fontSize) 973 | layerFont = CTFontCreateUIFontForLanguage(CTFontUIFontType.UIFontSystem, fontSize, nil) 974 | } 975 | //return NSAttributedString(string: string as NSString, attributes: [NSFontAttributeName: layerFont!]) 976 | return NSAttributedString(string: string as NSString, attributes: [kCTFontAttributeName: layerFont!]) 977 | } else { 978 | return NSAttributedString(string: "") 979 | } 980 | } 981 | func sizeToFit() { 982 | #if os(iOS) 983 | bounds = CGRect(origin: CGPointZero, size: self.attributedString.size()) 984 | #else 985 | bounds = CGRect(origin: CGPointZero, size: self.attributedString.size) 986 | #endif 987 | } 988 | } -------------------------------------------------------------------------------- /SwiftChartsMac/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SwiftChartsMac 4 | // 5 | // Created by Kevin Brewster on 1/20/15. 6 | // Copyright (c) 2015 KevinBrewster. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | 15 | 16 | func applicationDidFinishLaunching(aNotification: NSNotification) { 17 | // Insert code here to initialize your application 18 | } 19 | 20 | func applicationWillTerminate(aNotification: NSNotification) { 21 | // Insert code here to tear down your application 22 | } 23 | 24 | 25 | } 26 | 27 | -------------------------------------------------------------------------------- /SwiftChartsMac/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 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | Default 510 | 511 | 512 | 513 | 514 | 515 | 516 | Left to Right 517 | 518 | 519 | 520 | 521 | 522 | 523 | Right to Left 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | Default 535 | 536 | 537 | 538 | 539 | 540 | 541 | Left to Right 542 | 543 | 544 | 545 | 546 | 547 | 548 | Right to Left 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | -------------------------------------------------------------------------------- /SwiftChartsMac/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /SwiftChartsMac/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.KevinBrewster.$(PRODUCT_NAME:rfc1034identifier) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2015 KevinBrewster. All rights reserved. 29 | NSMainStoryboardFile 30 | Main 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /SwiftChartsMac/View Controllers/AdvancedLineChartViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SensorTagMac 4 | // 5 | // Created by Kevin Brewster on 1/12/15. 6 | // Copyright (c) 2015 KevinBrewster. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class AdvancedLineChartViewController: NSViewController { 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | if let lineChart = view.layer as? LineChart { 16 | 17 | let dataset1 = LineChart.Dataset(label: "Data 1", data: [65.0, 59.0, 80.0, 81.0, 56.0, 55.0, 40.0]) 18 | dataset1.color = NSColor.redColor().CGColor 19 | dataset1.fillColor = nil 20 | dataset1.curve = .Bezier(0.3) 21 | 22 | let dataset2 = LineChart.Dataset(label: "Data 2", data: [28.0, 48.0, 40.0, 19.0, 86.0, 27.0, 90.0]) 23 | dataset2.color = NSColor.blueColor().CGColor 24 | dataset2.defaultPoint.path = CGPath.Rect(CGRect(x: -5.0, y: -5.0, width: 10.0, height: 10.0)) // use squares as the points 25 | dataset2.curve = .Bezier(0.3) 26 | 27 | lineChart.legendEnabled = false 28 | lineChart.datasets = [ dataset1, dataset2 ] 29 | } 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /SwiftChartsMac/View Controllers/DynamicLineChartViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SensorTagMac 4 | // 5 | // Created by Kevin Brewster on 1/12/15. 6 | // Copyright (c) 2015 KevinBrewster. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class DynamicLineChartViewController: NSViewController { 12 | var timer: NSTimer? 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | 17 | if let lineChart = view.layer as? LineChart { 18 | var data: [CGFloat] = [3.0, 4.0, 9.0, 11.0, 13.0, 15.0] 19 | lineChart.datasets += [ LineChart.Dataset(label: "My Data", data: data) ] 20 | } 21 | } 22 | func addTestData() { 23 | if let lineChart = view.layer as? LineChart { 24 | let line = lineChart.datasets.first! 25 | let value = arc4random() % 25 26 | line.data += [ CGFloat(value) ] 27 | } 28 | } 29 | override func viewDidAppear() { 30 | super.viewDidAppear() 31 | timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("addTestData"), userInfo: nil, repeats: true) 32 | } 33 | override func viewDidDisappear() { 34 | super.viewDidDisappear() 35 | timer!.invalidate() 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /SwiftChartsMac/View Controllers/LabeledLineChartViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SensorTagMac 4 | // 5 | // Created by Kevin Brewster on 1/12/15. 6 | // Copyright (c) 2015 KevinBrewster. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class LabeledLineChartViewController: NSViewController { 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | if let lineChart = view.layer as? LineChart { 16 | lineChart.xAxis.labels = ["January", "February", "March", "April", "June", "July", "August", "September", "October", "November", "December"] 17 | 18 | var data: [CGFloat] = [0.003, 0.004, 0.009, 0.011, 0.013, 0.015, 0.004, 0.003, 0.009, 0.0075, 0.0061] 19 | lineChart.datasets += [ LineChart.Dataset(label: "My Data", data: data) ] 20 | 21 | } 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /SwiftChartsMac/View Controllers/MultiAxisLineChartViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SensorTagMac 4 | // 5 | // Created by Kevin Brewster on 1/12/15. 6 | // Copyright (c) 2015 KevinBrewster. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class MultiAxisLineChartViewController: NSViewController { 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | if let lineChart = view.layer as? LineChart { 16 | var data1: [CGFloat] = [3.0, 4.0, 9.0, 11.0, 13.0, 15.0] 17 | lineChart.datasets += [ LineChart.Dataset(label: "One", data: data1) ] 18 | 19 | var axis2 = LineChart.Axis(alignment: .Right) 20 | var data2: [CGFloat] = [504040.0, 201050.0, 303001.0, 130049.0, 170021.0, 202003.0] 21 | lineChart.datasets += [ LineChart.Dataset(label: "Two", data: data2, yAxis: axis2) ] 22 | 23 | var axis3 = LineChart.Axis(alignment: .Right) 24 | var data3: [CGFloat] = [0.0021, 0.0056, 0.001, 0.003, 0.005, 0.002]; 25 | lineChart.datasets += [ LineChart.Dataset(label: "Three", data: data3, yAxis: axis3) ] 26 | } 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /SwiftChartsMac/View Controllers/SimpleLineChartViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SensorTagMac 4 | // 5 | // Created by Kevin Brewster on 1/12/15. 6 | // Copyright (c) 2015 KevinBrewster. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class SimpleLineChartViewController: NSViewController { 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | if let lineChart = view.layer as? LineChart { 16 | let data: [CGFloat] = [3.0, 4.0, 9.0, 11.0, 13.0, 15.0] 17 | lineChart.datasets += [ LineChart.Dataset(label: "My Data", data: data) ] 18 | } 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /SwiftChartsiOS/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SwiftCharts 4 | // 5 | // Created by Kevin Brewster on 1/20/15. 6 | // Copyright (c) 2015 KevinBrewster. 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 | -------------------------------------------------------------------------------- /SwiftChartsiOS/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /SwiftChartsiOS/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 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /SwiftChartsiOS/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "ipad", 5 | "size" : "29x29", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "ipad", 10 | "size" : "29x29", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "ipad", 15 | "size" : "40x40", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "ipad", 20 | "size" : "40x40", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "ipad", 25 | "size" : "76x76", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "ipad", 30 | "size" : "76x76", 31 | "scale" : "2x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /SwiftChartsiOS/Images.xcassets/first.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "first.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /SwiftChartsiOS/Images.xcassets/first.imageset/first.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinbrewster/SwiftCharts/7f6286496cb0a426b6c4d995f0f4ee6a537ca102/SwiftChartsiOS/Images.xcassets/first.imageset/first.pdf -------------------------------------------------------------------------------- /SwiftChartsiOS/Images.xcassets/second.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "second.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /SwiftChartsiOS/Images.xcassets/second.imageset/second.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinbrewster/SwiftCharts/7f6286496cb0a426b6c4d995f0f4ee6a537ca102/SwiftChartsiOS/Images.xcassets/second.imageset/second.pdf -------------------------------------------------------------------------------- /SwiftChartsiOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.KevinBrewster.$(PRODUCT_NAME:rfc1034identifier) 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 | UIStatusBarTintParameters 34 | 35 | UINavigationBar 36 | 37 | Style 38 | UIBarStyleDefault 39 | Translucent 40 | 41 | 42 | 43 | UISupportedInterfaceOrientations~ipad 44 | 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /SwiftChartsiOS/View Controllers/AdvancedLineChartViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SwiftChartsiOS 4 | // 5 | // Created by Kevin Brewster on 1/12/15. 6 | // Copyright (c) 2015 KevinBrewster. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AdvancedLineChartViewController: UIViewController { 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | if let lineChart = view.layer as? LineChart { 16 | 17 | let dataset1 = LineChart.Dataset(label: "Data 1", data: [65.0, 59.0, 80.0, 81.0, 56.0, 55.0, 40.0]) 18 | dataset1.color = UIColor.redColor().CGColor 19 | dataset1.fillColor = nil 20 | dataset1.curve = .Bezier(0.3) 21 | 22 | let dataset2 = LineChart.Dataset(label: "Data 2", data: [28.0, 48.0, 40.0, 19.0, 86.0, 27.0, 90.0]) 23 | dataset2.color = UIColor.blueColor().CGColor 24 | dataset2.defaultPoint.path = CGPath.Rect(CGRect(x: -5.0, y: -5.0, width: 10.0, height: 10.0)) // use squares as the points 25 | dataset2.curve = .Bezier(0.3) 26 | 27 | lineChart.legendEnabled = false 28 | lineChart.datasets = [ dataset1, dataset2 ] 29 | } 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /SwiftChartsiOS/View Controllers/DynamicLineChartViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SwiftChartsiOS 4 | // 5 | // Created by Kevin Brewster on 1/12/15. 6 | // Copyright (c) 2015 KevinBrewster. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DynamicLineChartViewController: UIViewController { 12 | var timer: NSTimer? 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | 17 | if let lineChart = view.layer as? LineChart { 18 | var data: [CGFloat] = [3.0, 4.0, 9.0, 11.0, 13.0, 15.0] 19 | lineChart.datasets += [ LineChart.Dataset(label: "My Data", data: data) ] 20 | } 21 | } 22 | func addTestData() { 23 | if let lineChart = view.layer as? LineChart { 24 | let line = lineChart.datasets.first! 25 | let value = arc4random() % 25 26 | line.data += [ CGFloat(value) ] 27 | } 28 | } 29 | override func viewDidAppear(animated: Bool) { 30 | super.viewDidAppear(animated) 31 | timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("addTestData"), userInfo: nil, repeats: true) 32 | } 33 | override func viewDidDisappear(animated: Bool) { 34 | super.viewDidDisappear(animated) 35 | timer!.invalidate() 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /SwiftChartsiOS/View Controllers/LabeledLineChartViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SwiftChartsiOS 4 | // 5 | // Created by Kevin Brewster on 1/12/15. 6 | // Copyright (c) 2015 KevinBrewster. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class LabeledLineChartViewController: UIViewController { 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | if let lineChart = view.layer as? LineChart { 16 | lineChart.xAxis.labels = ["January", "February", "March", "April", "June", "July", "August", "September", "October", "November", "December"] 17 | 18 | var data: [CGFloat] = [0.003, 0.004, 0.009, 0.011, 0.013, 0.015, 0.004, 0.003, 0.009, 0.0075, 0.0061] 19 | lineChart.datasets += [ LineChart.Dataset(label: "My Data", data: data) ] 20 | 21 | } 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /SwiftChartsiOS/View Controllers/MultiAxisLineChartViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SwiftChartsiOS 4 | // 5 | // Created by Kevin Brewster on 1/12/15. 6 | // Copyright (c) 2015 KevinBrewster. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MultiAxisLineChartViewController: UIViewController { 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | if let lineChart = view.layer as? LineChart { 16 | 17 | var data1: [CGFloat] = [3.0, 4.0, 9.0, 11.0, 13.0, 15.0] 18 | lineChart.datasets += [ LineChart.Dataset(label: "One", data: data1) ] 19 | 20 | var axis2 = LineChart.Axis(alignment: .Right) 21 | var data2: [CGFloat] = [504040.0, 101050.0, 303001.0, 130049.0, 170021.0, 202003.0] 22 | lineChart.datasets += [ LineChart.Dataset(label: "Two", data: data2, yAxis: axis2) ] 23 | 24 | var axis3 = LineChart.Axis(alignment: .Right) 25 | var data3: [CGFloat] = [0.0021, 0.0056, 0.001, 0.003, 0.005, 0.002]; 26 | lineChart.datasets += [ LineChart.Dataset(label: "Three", data: data3, yAxis: axis3) ] 27 | 28 | } 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /SwiftChartsiOS/View Controllers/SimpleLineChartViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SwiftChartsiOS 4 | // 5 | // Created by Kevin Brewster on 1/12/15. 6 | // Copyright (c) 2015 KevinBrewster. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SimpleLineChartViewController: UIViewController { 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | if let lineChart = view.layer as? LineChart { 16 | let data: [CGFloat] = [3.0, 4.0, 9.0, 11.0, 13.0, 15.0] 17 | lineChart.datasets += [ LineChart.Dataset(label: "My Data", data: data) ] 18 | } 19 | } 20 | override func viewDidAppear(animated: Bool) { 21 | super.viewDidAppear(animated) 22 | println("view.bounds = \(view.bounds)") 23 | } 24 | } 25 | 26 | --------------------------------------------------------------------------------