├── .gitignore ├── README.md ├── linechart.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── linechart.xccheckout │ └── xcuserdata │ │ ├── zeissmirco.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ │ └── zemirco.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ ├── zeissmirco.xcuserdatad │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ │ ├── linechart.xcscheme │ │ └── xcschememanagement.plist │ └── zemirco.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── linechart.xcscheme │ └── xcschememanagement.plist ├── linechart ├── AppDelegate.swift ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json ├── Info.plist ├── LineChart.swift └── MainViewController.swift └── linechartTests ├── Info.plist └── linechartTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | gif.gif 3 | video.mov 4 | *.gif 5 | screenshots/ 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift LineChart 2 | 3 | ![line chart demo](https://s3.amazonaws.com/zeMirco/github/swift-linechart/gif30.gif) 4 | 5 | ## Usage 6 | 7 | ```swift 8 | var lineChart = LineChart() 9 | lineChart.addLine([3, 4, 9, 11, 13, 15]) 10 | ``` 11 | 12 | ## Features 13 | 14 | - Super simple 15 | - Highly customizable 16 | - Auto scaling 17 | - Touch enabled 18 | - Area below lines 19 | 20 | ## Properties 21 | 22 | Both `x` and `y` properties are of type `Coordinate`. 23 | Each can be customized separately and has its own settings for labels, gridlines and axis. 24 | 25 | - `labels`: Labels 26 | - `grid`: Grid 27 | - `axis`: Axis 28 | 29 | `Labels` can be switched on and off and they can have custom values. 30 | 31 | - `visible`: Bool = `true` 32 | - `values`: [String] = `[]` 33 | 34 | `Grid` can also be switched on/off, has a custom color and you can specify how many gridlines 35 | you'd like to show. 36 | 37 | - `visible`: Bool = `true` 38 | - `count`: CGFloat = `10` 39 | - `color`: UIColor = `UIColor(red: 238/255.0, green: 238/255.0, blue: 238/255.0, alpha: 1) // #eeeeee` 40 | 41 | `Axis` can be switched on/off, has a property to its color and you can specify how much the axis 42 | is inset from the border of your UIView. 43 | 44 | - `visible`: Bool = `true` 45 | - `color`: UIColor = `UIColor(red: 96/255.0, green: 125/255.0, blue: 139/255.0, alpha: 1) // 607d8b` 46 | - `inset`: CGFloat = `15` 47 | 48 | Animations can be customized through the `Animation` settings. 49 | 50 | - `enabled`: Bool = `true` 51 | - `duration`: CFTimeInterval = `1` 52 | 53 | If you'd like to show extra dots at your data points use the `Dots` features. 54 | 55 | - `visible`: Bool = `true` 56 | - `color`: UIColor = `UIColor.whiteColor()` 57 | - `innerRadius`: CGFloat = `8` 58 | - `outerRadius`: CGFloat = `12` 59 | - `innerRadiusHighlighted`: CGFloat = `8` 60 | - `outerRadiusHighlighted`: CGFloat = `12` 61 | 62 | In addition to the above mentioned features you can further customize your chart. 63 | 64 | - `area`: Bool = `true` - Fill the area between line and x axis 65 | - `lineWidth`: CGFloat = `2` - Set the line width 66 | - `colors`: [UIColor] = `[...]` - Colors for your line charts 67 | 68 | ## Methods 69 | 70 | Add line to chart. 71 | 72 | `lineChart.addLine(data: [CGFloat])` 73 | 74 | Remove charts, areas and labels but keep axis and grid. 75 | 76 | `lineChart.clear()` 77 | 78 | Make whole UIView white again 79 | 80 | `lineChart.clearAll()` 81 | 82 | ## Delegates 83 | 84 | `didSelectDataPoint()` 85 | 86 | Touch event happened at or close to data point. 87 | 88 | ```swift 89 | func didSelectDataPoint(x: CGFloat, yValues: [CGFloat]) { 90 | println("\(x) and \(yValues)") 91 | } 92 | ``` 93 | 94 | ## Examples 95 | 96 | #### Single line with default settings. 97 | 98 | ![line chart demo](https://s3.amazonaws.com/zeMirco/github/swift-linechart/01.png) 99 | 100 | ```swift 101 | var lineChart = LineChart() 102 | lineChart.addLine([3, 4, 9, 11, 13, 15]) 103 | ``` 104 | 105 | #### Two lines without grid and dots. 106 | 107 | ![two lines without grid and dots](https://s3.amazonaws.com/zeMirco/github/swift-linechart/02.png) 108 | 109 | ```swift 110 | var lineChart = LineChart() 111 | lineChart.area = false 112 | lineChart.x.grid.visible = false 113 | lineChart.x.labels.visible = false 114 | lineChart.y.grid.visible = false 115 | lineChart.y.labels.visible = false 116 | lineChart.dots.visible = false 117 | lineChart.addLine([3, 4, 9, 11, 13, 15]) 118 | lineChart.addLine([5, 4, 3, 6, 6, 7]) 119 | ``` 120 | 121 | #### Show x and y axis 122 | 123 | ![chart with x and y axis](https://s3.amazonaws.com/zeMirco/github/swift-linechart/04.png) 124 | 125 | ```swift 126 | var lineChart = LineChart() 127 | lineChart.area = false 128 | lineChart.x.grid.count = 5 129 | lineChart.y.grid.count = 5 130 | lineChart.addLine([3, 4, 9, 11, 13, 15]) 131 | lineChart.addLine([5, 4, 3, 6, 6, 7]) 132 | ``` 133 | 134 | 135 | ## License 136 | 137 | MIT 138 | -------------------------------------------------------------------------------- /linechart.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C06F8E1B1962FFD7000AC9AF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C06F8E1A1962FFD7000AC9AF /* AppDelegate.swift */; }; 11 | C06F8E1D1962FFD7000AC9AF /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C06F8E1C1962FFD7000AC9AF /* Images.xcassets */; }; 12 | C06F8E291962FFD7000AC9AF /* linechartTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C06F8E281962FFD7000AC9AF /* linechartTests.swift */; }; 13 | C06F8E331962FFEC000AC9AF /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C06F8E321962FFEC000AC9AF /* MainViewController.swift */; }; 14 | C06F8E3519630045000AC9AF /* LineChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = C06F8E3419630045000AC9AF /* LineChart.swift */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXContainerItemProxy section */ 18 | C06F8E231962FFD7000AC9AF /* PBXContainerItemProxy */ = { 19 | isa = PBXContainerItemProxy; 20 | containerPortal = C06F8E0D1962FFD7000AC9AF /* Project object */; 21 | proxyType = 1; 22 | remoteGlobalIDString = C06F8E141962FFD7000AC9AF; 23 | remoteInfo = linechart; 24 | }; 25 | /* End PBXContainerItemProxy section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | C06F8E151962FFD7000AC9AF /* linechart.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = linechart.app; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | C06F8E191962FFD7000AC9AF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 30 | C06F8E1A1962FFD7000AC9AF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 31 | C06F8E1C1962FFD7000AC9AF /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 32 | C06F8E221962FFD7000AC9AF /* linechartTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = linechartTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | C06F8E271962FFD7000AC9AF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 34 | C06F8E281962FFD7000AC9AF /* linechartTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = linechartTests.swift; sourceTree = ""; }; 35 | C06F8E321962FFEC000AC9AF /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; 36 | C06F8E3419630045000AC9AF /* LineChart.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineChart.swift; sourceTree = ""; }; 37 | /* End PBXFileReference section */ 38 | 39 | /* Begin PBXFrameworksBuildPhase section */ 40 | C06F8E121962FFD7000AC9AF /* Frameworks */ = { 41 | isa = PBXFrameworksBuildPhase; 42 | buildActionMask = 2147483647; 43 | files = ( 44 | ); 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | C06F8E1F1962FFD7000AC9AF /* Frameworks */ = { 48 | isa = PBXFrameworksBuildPhase; 49 | buildActionMask = 2147483647; 50 | files = ( 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | /* End PBXFrameworksBuildPhase section */ 55 | 56 | /* Begin PBXGroup section */ 57 | C06F8E0C1962FFD7000AC9AF = { 58 | isa = PBXGroup; 59 | children = ( 60 | C06F8E171962FFD7000AC9AF /* linechart */, 61 | C06F8E251962FFD7000AC9AF /* linechartTests */, 62 | C06F8E161962FFD7000AC9AF /* Products */, 63 | ); 64 | sourceTree = ""; 65 | }; 66 | C06F8E161962FFD7000AC9AF /* Products */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | C06F8E151962FFD7000AC9AF /* linechart.app */, 70 | C06F8E221962FFD7000AC9AF /* linechartTests.xctest */, 71 | ); 72 | name = Products; 73 | sourceTree = ""; 74 | }; 75 | C06F8E171962FFD7000AC9AF /* linechart */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | C06F8E321962FFEC000AC9AF /* MainViewController.swift */, 79 | C06F8E3419630045000AC9AF /* LineChart.swift */, 80 | C06F8E181962FFD7000AC9AF /* Supporting Files */, 81 | ); 82 | path = linechart; 83 | sourceTree = ""; 84 | }; 85 | C06F8E181962FFD7000AC9AF /* Supporting Files */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | C06F8E1A1962FFD7000AC9AF /* AppDelegate.swift */, 89 | C06F8E1C1962FFD7000AC9AF /* Images.xcassets */, 90 | C06F8E191962FFD7000AC9AF /* Info.plist */, 91 | ); 92 | name = "Supporting Files"; 93 | sourceTree = ""; 94 | }; 95 | C06F8E251962FFD7000AC9AF /* linechartTests */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | C06F8E281962FFD7000AC9AF /* linechartTests.swift */, 99 | C06F8E261962FFD7000AC9AF /* Supporting Files */, 100 | ); 101 | path = linechartTests; 102 | sourceTree = ""; 103 | }; 104 | C06F8E261962FFD7000AC9AF /* Supporting Files */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | C06F8E271962FFD7000AC9AF /* Info.plist */, 108 | ); 109 | name = "Supporting Files"; 110 | sourceTree = ""; 111 | }; 112 | /* End PBXGroup section */ 113 | 114 | /* Begin PBXNativeTarget section */ 115 | C06F8E141962FFD7000AC9AF /* linechart */ = { 116 | isa = PBXNativeTarget; 117 | buildConfigurationList = C06F8E2C1962FFD7000AC9AF /* Build configuration list for PBXNativeTarget "linechart" */; 118 | buildPhases = ( 119 | C06F8E111962FFD7000AC9AF /* Sources */, 120 | C06F8E121962FFD7000AC9AF /* Frameworks */, 121 | C06F8E131962FFD7000AC9AF /* Resources */, 122 | ); 123 | buildRules = ( 124 | ); 125 | dependencies = ( 126 | ); 127 | name = linechart; 128 | productName = linechart; 129 | productReference = C06F8E151962FFD7000AC9AF /* linechart.app */; 130 | productType = "com.apple.product-type.application"; 131 | }; 132 | C06F8E211962FFD7000AC9AF /* linechartTests */ = { 133 | isa = PBXNativeTarget; 134 | buildConfigurationList = C06F8E2F1962FFD7000AC9AF /* Build configuration list for PBXNativeTarget "linechartTests" */; 135 | buildPhases = ( 136 | C06F8E1E1962FFD7000AC9AF /* Sources */, 137 | C06F8E1F1962FFD7000AC9AF /* Frameworks */, 138 | C06F8E201962FFD7000AC9AF /* Resources */, 139 | ); 140 | buildRules = ( 141 | ); 142 | dependencies = ( 143 | C06F8E241962FFD7000AC9AF /* PBXTargetDependency */, 144 | ); 145 | name = linechartTests; 146 | productName = linechartTests; 147 | productReference = C06F8E221962FFD7000AC9AF /* linechartTests.xctest */; 148 | productType = "com.apple.product-type.bundle.unit-test"; 149 | }; 150 | /* End PBXNativeTarget section */ 151 | 152 | /* Begin PBXProject section */ 153 | C06F8E0D1962FFD7000AC9AF /* Project object */ = { 154 | isa = PBXProject; 155 | attributes = { 156 | LastSwiftMigration = 0700; 157 | LastSwiftUpdateCheck = 0700; 158 | LastUpgradeCheck = 0800; 159 | ORGANIZATIONNAME = zemirco; 160 | TargetAttributes = { 161 | C06F8E141962FFD7000AC9AF = { 162 | CreatedOnToolsVersion = 6.0; 163 | LastSwiftMigration = 0800; 164 | }; 165 | C06F8E211962FFD7000AC9AF = { 166 | CreatedOnToolsVersion = 6.0; 167 | LastSwiftMigration = 0800; 168 | TestTargetID = C06F8E141962FFD7000AC9AF; 169 | }; 170 | }; 171 | }; 172 | buildConfigurationList = C06F8E101962FFD7000AC9AF /* Build configuration list for PBXProject "linechart" */; 173 | compatibilityVersion = "Xcode 3.2"; 174 | developmentRegion = English; 175 | hasScannedForEncodings = 0; 176 | knownRegions = ( 177 | en, 178 | ); 179 | mainGroup = C06F8E0C1962FFD7000AC9AF; 180 | productRefGroup = C06F8E161962FFD7000AC9AF /* Products */; 181 | projectDirPath = ""; 182 | projectRoot = ""; 183 | targets = ( 184 | C06F8E141962FFD7000AC9AF /* linechart */, 185 | C06F8E211962FFD7000AC9AF /* linechartTests */, 186 | ); 187 | }; 188 | /* End PBXProject section */ 189 | 190 | /* Begin PBXResourcesBuildPhase section */ 191 | C06F8E131962FFD7000AC9AF /* Resources */ = { 192 | isa = PBXResourcesBuildPhase; 193 | buildActionMask = 2147483647; 194 | files = ( 195 | C06F8E1D1962FFD7000AC9AF /* Images.xcassets in Resources */, 196 | ); 197 | runOnlyForDeploymentPostprocessing = 0; 198 | }; 199 | C06F8E201962FFD7000AC9AF /* Resources */ = { 200 | isa = PBXResourcesBuildPhase; 201 | buildActionMask = 2147483647; 202 | files = ( 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | }; 206 | /* End PBXResourcesBuildPhase section */ 207 | 208 | /* Begin PBXSourcesBuildPhase section */ 209 | C06F8E111962FFD7000AC9AF /* Sources */ = { 210 | isa = PBXSourcesBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | C06F8E1B1962FFD7000AC9AF /* AppDelegate.swift in Sources */, 214 | C06F8E331962FFEC000AC9AF /* MainViewController.swift in Sources */, 215 | C06F8E3519630045000AC9AF /* LineChart.swift in Sources */, 216 | ); 217 | runOnlyForDeploymentPostprocessing = 0; 218 | }; 219 | C06F8E1E1962FFD7000AC9AF /* Sources */ = { 220 | isa = PBXSourcesBuildPhase; 221 | buildActionMask = 2147483647; 222 | files = ( 223 | C06F8E291962FFD7000AC9AF /* linechartTests.swift in Sources */, 224 | ); 225 | runOnlyForDeploymentPostprocessing = 0; 226 | }; 227 | /* End PBXSourcesBuildPhase section */ 228 | 229 | /* Begin PBXTargetDependency section */ 230 | C06F8E241962FFD7000AC9AF /* PBXTargetDependency */ = { 231 | isa = PBXTargetDependency; 232 | target = C06F8E141962FFD7000AC9AF /* linechart */; 233 | targetProxy = C06F8E231962FFD7000AC9AF /* PBXContainerItemProxy */; 234 | }; 235 | /* End PBXTargetDependency section */ 236 | 237 | /* Begin XCBuildConfiguration section */ 238 | C06F8E2A1962FFD7000AC9AF /* Debug */ = { 239 | isa = XCBuildConfiguration; 240 | buildSettings = { 241 | ALWAYS_SEARCH_USER_PATHS = NO; 242 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 243 | CLANG_CXX_LIBRARY = "libc++"; 244 | CLANG_ENABLE_MODULES = YES; 245 | CLANG_ENABLE_OBJC_ARC = YES; 246 | CLANG_WARN_BOOL_CONVERSION = YES; 247 | CLANG_WARN_CONSTANT_CONVERSION = YES; 248 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 249 | CLANG_WARN_EMPTY_BODY = YES; 250 | CLANG_WARN_ENUM_CONVERSION = YES; 251 | CLANG_WARN_INFINITE_RECURSION = YES; 252 | CLANG_WARN_INT_CONVERSION = YES; 253 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 254 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 255 | CLANG_WARN_UNREACHABLE_CODE = YES; 256 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 257 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 258 | COPY_PHASE_STRIP = NO; 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_SYMBOLS_PRIVATE_EXTERN = NO; 270 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 271 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 272 | GCC_WARN_UNDECLARED_SELECTOR = YES; 273 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 274 | GCC_WARN_UNUSED_FUNCTION = YES; 275 | GCC_WARN_UNUSED_VARIABLE = YES; 276 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 277 | METAL_ENABLE_DEBUG_INFO = YES; 278 | ONLY_ACTIVE_ARCH = YES; 279 | SDKROOT = iphoneos; 280 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 281 | }; 282 | name = Debug; 283 | }; 284 | C06F8E2B1962FFD7000AC9AF /* Release */ = { 285 | isa = XCBuildConfiguration; 286 | buildSettings = { 287 | ALWAYS_SEARCH_USER_PATHS = NO; 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_INFINITE_RECURSION = YES; 298 | CLANG_WARN_INT_CONVERSION = YES; 299 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 300 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 301 | CLANG_WARN_UNREACHABLE_CODE = YES; 302 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 303 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 304 | COPY_PHASE_STRIP = YES; 305 | ENABLE_NS_ASSERTIONS = NO; 306 | ENABLE_STRICT_OBJC_MSGSEND = YES; 307 | GCC_C_LANGUAGE_STANDARD = gnu99; 308 | GCC_NO_COMMON_BLOCKS = YES; 309 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 310 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 311 | GCC_WARN_UNDECLARED_SELECTOR = YES; 312 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 313 | GCC_WARN_UNUSED_FUNCTION = YES; 314 | GCC_WARN_UNUSED_VARIABLE = YES; 315 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 316 | METAL_ENABLE_DEBUG_INFO = NO; 317 | SDKROOT = iphoneos; 318 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 319 | VALIDATE_PRODUCT = YES; 320 | }; 321 | name = Release; 322 | }; 323 | C06F8E2D1962FFD7000AC9AF /* Debug */ = { 324 | isa = XCBuildConfiguration; 325 | buildSettings = { 326 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 327 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 328 | INFOPLIST_FILE = linechart/Info.plist; 329 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 330 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 331 | PRODUCT_BUNDLE_IDENTIFIER = "com.example.${PRODUCT_NAME:rfc1034identifier}"; 332 | PRODUCT_NAME = "$(TARGET_NAME)"; 333 | SWIFT_VERSION = 3.0; 334 | }; 335 | name = Debug; 336 | }; 337 | C06F8E2E1962FFD7000AC9AF /* Release */ = { 338 | isa = XCBuildConfiguration; 339 | buildSettings = { 340 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 341 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 342 | INFOPLIST_FILE = linechart/Info.plist; 343 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 344 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 345 | PRODUCT_BUNDLE_IDENTIFIER = "com.example.${PRODUCT_NAME:rfc1034identifier}"; 346 | PRODUCT_NAME = "$(TARGET_NAME)"; 347 | SWIFT_VERSION = 3.0; 348 | }; 349 | name = Release; 350 | }; 351 | C06F8E301962FFD7000AC9AF /* Debug */ = { 352 | isa = XCBuildConfiguration; 353 | buildSettings = { 354 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/linechart.app/linechart"; 355 | FRAMEWORK_SEARCH_PATHS = ( 356 | "$(SDKROOT)/Developer/Library/Frameworks", 357 | "$(inherited)", 358 | ); 359 | GCC_PREPROCESSOR_DEFINITIONS = ( 360 | "DEBUG=1", 361 | "$(inherited)", 362 | ); 363 | INFOPLIST_FILE = linechartTests/Info.plist; 364 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 365 | METAL_ENABLE_DEBUG_INFO = YES; 366 | PRODUCT_BUNDLE_IDENTIFIER = "com.example.${PRODUCT_NAME:rfc1034identifier}"; 367 | PRODUCT_NAME = "$(TARGET_NAME)"; 368 | SWIFT_VERSION = 3.0; 369 | TEST_HOST = "$(BUNDLE_LOADER)"; 370 | }; 371 | name = Debug; 372 | }; 373 | C06F8E311962FFD7000AC9AF /* Release */ = { 374 | isa = XCBuildConfiguration; 375 | buildSettings = { 376 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/linechart.app/linechart"; 377 | FRAMEWORK_SEARCH_PATHS = ( 378 | "$(SDKROOT)/Developer/Library/Frameworks", 379 | "$(inherited)", 380 | ); 381 | INFOPLIST_FILE = linechartTests/Info.plist; 382 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 383 | METAL_ENABLE_DEBUG_INFO = NO; 384 | PRODUCT_BUNDLE_IDENTIFIER = "com.example.${PRODUCT_NAME:rfc1034identifier}"; 385 | PRODUCT_NAME = "$(TARGET_NAME)"; 386 | SWIFT_VERSION = 3.0; 387 | TEST_HOST = "$(BUNDLE_LOADER)"; 388 | }; 389 | name = Release; 390 | }; 391 | /* End XCBuildConfiguration section */ 392 | 393 | /* Begin XCConfigurationList section */ 394 | C06F8E101962FFD7000AC9AF /* Build configuration list for PBXProject "linechart" */ = { 395 | isa = XCConfigurationList; 396 | buildConfigurations = ( 397 | C06F8E2A1962FFD7000AC9AF /* Debug */, 398 | C06F8E2B1962FFD7000AC9AF /* Release */, 399 | ); 400 | defaultConfigurationIsVisible = 0; 401 | defaultConfigurationName = Release; 402 | }; 403 | C06F8E2C1962FFD7000AC9AF /* Build configuration list for PBXNativeTarget "linechart" */ = { 404 | isa = XCConfigurationList; 405 | buildConfigurations = ( 406 | C06F8E2D1962FFD7000AC9AF /* Debug */, 407 | C06F8E2E1962FFD7000AC9AF /* Release */, 408 | ); 409 | defaultConfigurationIsVisible = 0; 410 | defaultConfigurationName = Release; 411 | }; 412 | C06F8E2F1962FFD7000AC9AF /* Build configuration list for PBXNativeTarget "linechartTests" */ = { 413 | isa = XCConfigurationList; 414 | buildConfigurations = ( 415 | C06F8E301962FFD7000AC9AF /* Debug */, 416 | C06F8E311962FFD7000AC9AF /* Release */, 417 | ); 418 | defaultConfigurationIsVisible = 0; 419 | defaultConfigurationName = Release; 420 | }; 421 | /* End XCConfigurationList section */ 422 | }; 423 | rootObject = C06F8E0D1962FFD7000AC9AF /* Project object */; 424 | } 425 | -------------------------------------------------------------------------------- /linechart.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /linechart.xcodeproj/project.xcworkspace/xcshareddata/linechart.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 026D4624-F6DE-4290-9A13-71AD138CBB5A 9 | IDESourceControlProjectName 10 | linechart 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 3C3ECE664297FF57C9A111F3256C375D049C6FE1 14 | https://github.com/zemirco/swift-linechart 15 | 16 | IDESourceControlProjectPath 17 | linechart/linechart.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 3C3ECE664297FF57C9A111F3256C375D049C6FE1 21 | ../../.. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/zemirco/swift-linechart 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | 3C3ECE664297FF57C9A111F3256C375D049C6FE1 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 3C3ECE664297FF57C9A111F3256C375D049C6FE1 36 | IDESourceControlWCCName 37 | swift-linechart 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /linechart.xcodeproj/project.xcworkspace/xcuserdata/zeissmirco.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zemirco/swift-linechart/103be063457e71a28bd064ab569e2efac1914fd5/linechart.xcodeproj/project.xcworkspace/xcuserdata/zeissmirco.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /linechart.xcodeproj/project.xcworkspace/xcuserdata/zemirco.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zemirco/swift-linechart/103be063457e71a28bd064ab569e2efac1914fd5/linechart.xcodeproj/project.xcworkspace/xcuserdata/zemirco.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /linechart.xcodeproj/xcuserdata/zeissmirco.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /linechart.xcodeproj/xcuserdata/zeissmirco.xcuserdatad/xcschemes/linechart.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /linechart.xcodeproj/xcuserdata/zeissmirco.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | linechart.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | C06F8E141962FFD7000AC9AF 16 | 17 | primary 18 | 19 | 20 | C06F8E211962FFD7000AC9AF 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /linechart.xcodeproj/xcuserdata/zemirco.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /linechart.xcodeproj/xcuserdata/zemirco.xcuserdatad/xcschemes/linechart.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /linechart.xcodeproj/xcuserdata/zemirco.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | linechart.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | C06F8E141962FFD7000AC9AF 16 | 17 | primary 18 | 19 | 20 | C06F8E211962FFD7000AC9AF 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /linechart/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | 2 | import UIKit 3 | 4 | @UIApplicationMain 5 | class AppDelegate: UIResponder, UIApplicationDelegate { 6 | 7 | var window: UIWindow? 8 | 9 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 10 | self.window = UIWindow(frame: UIScreen.main.bounds) 11 | self.window!.backgroundColor = UIColor.white 12 | self.window!.rootViewController = MainViewController() 13 | self.window!.makeKeyAndVisible() 14 | return true 15 | } 16 | 17 | func applicationWillResignActive(_ application: UIApplication) {} 18 | 19 | func applicationDidEnterBackground(_ application: UIApplication) {} 20 | 21 | func applicationWillEnterForeground(_ application: UIApplication) {} 22 | 23 | func applicationDidBecomeActive(_ application: UIApplication) {} 24 | 25 | func applicationWillTerminate(_ application: UIApplication) {} 26 | 27 | 28 | } 29 | 30 | -------------------------------------------------------------------------------- /linechart/Images.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" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /linechart/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /linechart/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 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /linechart/LineChart.swift: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import UIKit 5 | import QuartzCore 6 | 7 | // delegate method 8 | public protocol LineChartDelegate { 9 | func didSelectDataPoint(_ x: CGFloat, yValues: [CGFloat]) 10 | } 11 | 12 | /** 13 | * LineChart 14 | */ 15 | open class LineChart: UIView { 16 | 17 | /** 18 | * Helpers class 19 | */ 20 | fileprivate class Helpers { 21 | 22 | /** 23 | * Convert hex color to UIColor 24 | */ 25 | fileprivate class func UIColorFromHex(_ hex: Int) -> UIColor { 26 | let red = CGFloat((hex & 0xFF0000) >> 16) / 255.0 27 | let green = CGFloat((hex & 0xFF00) >> 8) / 255.0 28 | let blue = CGFloat((hex & 0xFF)) / 255.0 29 | return UIColor(red: red, green: green, blue: blue, alpha: 1) 30 | } 31 | 32 | /** 33 | * Lighten color. 34 | */ 35 | fileprivate class func lightenUIColor(_ color: UIColor) -> UIColor { 36 | var h: CGFloat = 0 37 | var s: CGFloat = 0 38 | var b: CGFloat = 0 39 | var a: CGFloat = 0 40 | color.getHue(&h, saturation: &s, brightness: &b, alpha: &a) 41 | return UIColor(hue: h, saturation: s, brightness: b * 1.5, alpha: a) 42 | } 43 | } 44 | 45 | public struct Labels { 46 | public var visible: Bool = true 47 | public var values: [String] = [] 48 | } 49 | 50 | public struct Grid { 51 | public var visible: Bool = true 52 | public var count: CGFloat = 10 53 | // #eeeeee 54 | public var color: UIColor = UIColor(red: 238/255.0, green: 238/255.0, blue: 238/255.0, alpha: 1) 55 | } 56 | 57 | public struct Axis { 58 | public var visible: Bool = true 59 | // #607d8b 60 | public var color: UIColor = UIColor(red: 96/255.0, green: 125/255.0, blue: 139/255.0, alpha: 1) 61 | public var inset: CGFloat = 15 62 | } 63 | 64 | public struct Coordinate { 65 | // public 66 | public var labels: Labels = Labels() 67 | public var grid: Grid = Grid() 68 | public var axis: Axis = Axis() 69 | 70 | // private 71 | fileprivate var linear: LinearScale! 72 | fileprivate var scale: ((CGFloat) -> CGFloat)! 73 | fileprivate var invert: ((CGFloat) -> CGFloat)! 74 | fileprivate var ticks: (CGFloat, CGFloat, CGFloat)! 75 | } 76 | 77 | public struct Animation { 78 | public var enabled: Bool = true 79 | public var duration: CFTimeInterval = 1 80 | } 81 | 82 | public struct Dots { 83 | public var visible: Bool = true 84 | public var color: UIColor = UIColor.white 85 | public var innerRadius: CGFloat = 8 86 | public var outerRadius: CGFloat = 12 87 | public var innerRadiusHighlighted: CGFloat = 8 88 | public var outerRadiusHighlighted: CGFloat = 12 89 | } 90 | 91 | // default configuration 92 | open var area: Bool = true 93 | open var animation: Animation = Animation() 94 | open var dots: Dots = Dots() 95 | open var lineWidth: CGFloat = 2 96 | 97 | open var x: Coordinate = Coordinate() 98 | open var y: Coordinate = Coordinate() 99 | 100 | 101 | // values calculated on init 102 | fileprivate var drawingHeight: CGFloat = 0 { 103 | didSet { 104 | let max = getMaximumValue() 105 | let min = getMinimumValue() 106 | y.linear = LinearScale(domain: [min, max], range: [0, drawingHeight]) 107 | y.scale = y.linear.scale() 108 | y.ticks = y.linear.ticks(Int(y.grid.count)) 109 | } 110 | } 111 | fileprivate var drawingWidth: CGFloat = 0 { 112 | didSet { 113 | let data = dataStore[0] 114 | x.linear = LinearScale(domain: [0.0, CGFloat(data.count - 1)], range: [0, drawingWidth]) 115 | x.scale = x.linear.scale() 116 | x.invert = x.linear.invert() 117 | x.ticks = x.linear.ticks(Int(x.grid.count)) 118 | } 119 | } 120 | 121 | open var delegate: LineChartDelegate? 122 | 123 | // data stores 124 | fileprivate var dataStore: [[CGFloat]] = [] 125 | fileprivate var dotsDataStore: [[DotCALayer]] = [] 126 | fileprivate var lineLayerStore: [CAShapeLayer] = [] 127 | 128 | fileprivate var removeAll: Bool = false 129 | 130 | // category10 colors from d3 - https://github.com/mbostock/d3/wiki/Ordinal-Scales 131 | open var colors: [UIColor] = [ 132 | UIColor(red: 0.121569, green: 0.466667, blue: 0.705882, alpha: 1), 133 | UIColor(red: 1, green: 0.498039, blue: 0.054902, alpha: 1), 134 | UIColor(red: 0.172549, green: 0.627451, blue: 0.172549, alpha: 1), 135 | UIColor(red: 0.839216, green: 0.152941, blue: 0.156863, alpha: 1), 136 | UIColor(red: 0.580392, green: 0.403922, blue: 0.741176, alpha: 1), 137 | UIColor(red: 0.54902, green: 0.337255, blue: 0.294118, alpha: 1), 138 | UIColor(red: 0.890196, green: 0.466667, blue: 0.760784, alpha: 1), 139 | UIColor(red: 0.498039, green: 0.498039, blue: 0.498039, alpha: 1), 140 | UIColor(red: 0.737255, green: 0.741176, blue: 0.133333, alpha: 1), 141 | UIColor(red: 0.0901961, green: 0.745098, blue: 0.811765, alpha: 1) 142 | ] 143 | 144 | override public init(frame: CGRect) { 145 | super.init(frame: frame) 146 | self.backgroundColor = UIColor.clear 147 | } 148 | 149 | convenience init() { 150 | self.init(frame: CGRect.zero) 151 | } 152 | 153 | required public init?(coder aDecoder: NSCoder) { 154 | super.init(coder: aDecoder) 155 | } 156 | 157 | override open func draw(_ rect: CGRect) { 158 | 159 | if removeAll { 160 | let context = UIGraphicsGetCurrentContext() 161 | context?.clear(rect) 162 | return 163 | } 164 | 165 | self.drawingHeight = self.bounds.height - (2 * y.axis.inset) 166 | self.drawingWidth = self.bounds.width - (2 * x.axis.inset) 167 | 168 | // remove all labels 169 | for view: AnyObject in self.subviews { 170 | view.removeFromSuperview() 171 | } 172 | 173 | // remove all lines on device rotation 174 | for lineLayer in lineLayerStore { 175 | lineLayer.removeFromSuperlayer() 176 | } 177 | lineLayerStore.removeAll() 178 | 179 | // remove all dots on device rotation 180 | for dotsData in dotsDataStore { 181 | for dot in dotsData { 182 | dot.removeFromSuperlayer() 183 | } 184 | } 185 | dotsDataStore.removeAll() 186 | 187 | // draw grid 188 | if x.grid.visible && y.grid.visible { drawGrid() } 189 | 190 | // draw axes 191 | if x.axis.visible && y.axis.visible { drawAxes() } 192 | 193 | // draw labels 194 | if x.labels.visible { drawXLabels() } 195 | if y.labels.visible { drawYLabels() } 196 | 197 | // draw lines 198 | for (lineIndex, _) in dataStore.enumerated() { 199 | 200 | drawLine(lineIndex) 201 | 202 | // draw dots 203 | if dots.visible { drawDataDots(lineIndex) } 204 | 205 | // draw area under line chart 206 | if area { drawAreaBeneathLineChart(lineIndex) } 207 | 208 | } 209 | 210 | } 211 | 212 | 213 | 214 | /** 215 | * Get y value for given x value. Or return zero or maximum value. 216 | */ 217 | fileprivate func getYValuesForXValue(_ x: Int) -> [CGFloat] { 218 | var result: [CGFloat] = [] 219 | for lineData in dataStore { 220 | if x < 0 { 221 | result.append(lineData[0]) 222 | } else if x > lineData.count - 1 { 223 | result.append(lineData[lineData.count - 1]) 224 | } else { 225 | result.append(lineData[x]) 226 | } 227 | } 228 | return result 229 | } 230 | 231 | 232 | 233 | /** 234 | * Handle touch events. 235 | */ 236 | fileprivate func handleTouchEvents(_ touches: NSSet!, event: UIEvent) { 237 | if (self.dataStore.isEmpty) { 238 | return 239 | } 240 | let point: AnyObject! = touches.anyObject() as AnyObject! 241 | let xValue = point.location(in: self).x 242 | let inverted = self.x.invert(xValue - x.axis.inset) 243 | let rounded = Int(round(Double(inverted))) 244 | let yValues: [CGFloat] = getYValuesForXValue(rounded) 245 | highlightDataPoints(rounded) 246 | delegate?.didSelectDataPoint(CGFloat(rounded), yValues: yValues) 247 | } 248 | 249 | 250 | 251 | /** 252 | * Listen on touch end event. 253 | */ 254 | override open func touchesEnded(_ touches: Set, with event: UIEvent?) { 255 | handleTouchEvents(touches as NSSet!, event: event!) 256 | } 257 | 258 | 259 | 260 | /** 261 | * Listen on touch move event 262 | */ 263 | override open func touchesMoved(_ touches: Set, with event: UIEvent?) { 264 | handleTouchEvents(touches as NSSet!, event: event!) 265 | } 266 | 267 | 268 | 269 | /** 270 | * Highlight data points at index. 271 | */ 272 | fileprivate func highlightDataPoints(_ index: Int) { 273 | for (lineIndex, dotsData) in dotsDataStore.enumerated() { 274 | // make all dots white again 275 | for dot in dotsData { 276 | dot.backgroundColor = dots.color.cgColor 277 | } 278 | // highlight current data point 279 | var dot: DotCALayer 280 | if index < 0 { 281 | dot = dotsData[0] 282 | } else if index > dotsData.count - 1 { 283 | dot = dotsData[dotsData.count - 1] 284 | } else { 285 | dot = dotsData[index] 286 | } 287 | dot.backgroundColor = Helpers.lightenUIColor(colors[lineIndex]).cgColor 288 | } 289 | } 290 | 291 | 292 | 293 | /** 294 | * Draw small dot at every data point. 295 | */ 296 | fileprivate func drawDataDots(_ lineIndex: Int) { 297 | var dotLayers: [DotCALayer] = [] 298 | var data = self.dataStore[lineIndex] 299 | 300 | for index in 0.. CGFloat { 355 | var max: CGFloat = 1 356 | for data in dataStore { 357 | let newMax = data.max()! 358 | if newMax > max { 359 | max = newMax 360 | } 361 | } 362 | return max 363 | } 364 | 365 | 366 | 367 | /** 368 | * Get maximum value in all arrays in data store. 369 | */ 370 | fileprivate func getMinimumValue() -> CGFloat { 371 | var min: CGFloat = 0 372 | for data in dataStore { 373 | let newMin = data.min()! 374 | if newMin < min { 375 | min = newMin 376 | } 377 | } 378 | return min 379 | } 380 | 381 | 382 | 383 | /** 384 | * Draw line. 385 | */ 386 | fileprivate func drawLine(_ lineIndex: Int) { 387 | 388 | var data = self.dataStore[lineIndex] 389 | let path = UIBezierPath() 390 | 391 | var xValue = self.x.scale(0) + x.axis.inset 392 | var yValue = self.bounds.height - self.y.scale(data[0]) - y.axis.inset 393 | path.move(to: CGPoint(x: xValue, y: yValue)) 394 | for index in 1.. (_ x: CGFloat) -> CGFloat { 628 | return bilinear(domain, range: range, uninterpolate: uninterpolate, interpolate: interpolate) 629 | } 630 | 631 | open func invert() -> (_ x: CGFloat) -> CGFloat { 632 | return bilinear(range, range: domain, uninterpolate: uninterpolate, interpolate: interpolate) 633 | } 634 | 635 | open func ticks(_ m: Int) -> (CGFloat, CGFloat, CGFloat) { 636 | return scale_linearTicks(domain, m: m) 637 | } 638 | 639 | fileprivate func scale_linearTicks(_ domain: [CGFloat], m: Int) -> (CGFloat, CGFloat, CGFloat) { 640 | return scale_linearTickRange(domain, m: m) 641 | } 642 | 643 | fileprivate func scale_linearTickRange(_ domain: [CGFloat], m: Int) -> (CGFloat, CGFloat, CGFloat) { 644 | var extent = scaleExtent(domain) 645 | let span = extent[1] - extent[0] 646 | var step = CGFloat(pow(10, floor(log(Double(span) / Double(m)) / M_LN10))) 647 | let err = CGFloat(m) / span * step 648 | 649 | // Filter ticks to get closer to the desired count. 650 | if (err <= 0.15) { 651 | step *= 10 652 | } else if (err <= 0.35) { 653 | step *= 5 654 | } else if (err <= 0.75) { 655 | step *= 2 656 | } 657 | 658 | // Round start and stop values to step interval. 659 | let start = ceil(extent[0] / step) * step 660 | let stop = floor(extent[1] / step) * step + step * 0.5 // inclusive 661 | 662 | return (start, stop, step) 663 | } 664 | 665 | fileprivate func scaleExtent(_ domain: [CGFloat]) -> [CGFloat] { 666 | let start = domain[0] 667 | let stop = domain[domain.count - 1] 668 | return start < stop ? [start, stop] : [stop, start] 669 | } 670 | 671 | fileprivate func interpolate(_ a: CGFloat, b: CGFloat) -> (_ c: CGFloat) -> CGFloat { 672 | var diff = b - a 673 | func f(_ c: CGFloat) -> CGFloat { 674 | return (a + diff) * c 675 | } 676 | return f 677 | } 678 | 679 | fileprivate func uninterpolate(_ a: CGFloat, b: CGFloat) -> (_ c: CGFloat) -> CGFloat { 680 | var diff = b - a 681 | var re = diff != 0 ? 1 / diff : 0 682 | func f(_ c: CGFloat) -> CGFloat { 683 | return (c - a) * re 684 | } 685 | return f 686 | } 687 | 688 | fileprivate func bilinear(_ domain: [CGFloat], range: [CGFloat], uninterpolate: (_ a: CGFloat, _ b: CGFloat) -> (_ c: CGFloat) -> CGFloat, interpolate: (_ a: CGFloat, _ b: CGFloat) -> (_ c: CGFloat) -> CGFloat) -> (_ c: CGFloat) -> CGFloat { 689 | var u: (_ c: CGFloat) -> CGFloat = uninterpolate(domain[0], domain[1]) 690 | var i: (_ c: CGFloat) -> CGFloat = interpolate(range[0], range[1]) 691 | func f(_ d: CGFloat) -> CGFloat { 692 | return i(u(d)) 693 | } 694 | return f 695 | } 696 | 697 | } 698 | -------------------------------------------------------------------------------- /linechart/MainViewController.swift: -------------------------------------------------------------------------------- 1 | 2 | import UIKit 3 | import QuartzCore 4 | 5 | class MainViewController: UIViewController, LineChartDelegate { 6 | 7 | 8 | 9 | var label = UILabel() 10 | var lineChart: LineChart! 11 | 12 | 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | 17 | var views: [String: AnyObject] = [:] 18 | 19 | label.text = "..." 20 | label.translatesAutoresizingMaskIntoConstraints = false 21 | label.textAlignment = NSTextAlignment.center 22 | self.view.addSubview(label) 23 | views["label"] = label 24 | view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[label]-|", options: [], metrics: nil, views: views)) 25 | view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-80-[label]", options: [], metrics: nil, views: views)) 26 | 27 | // simple arrays 28 | let data: [CGFloat] = [3, 4, -2, 11, 13, 15] 29 | let data2: [CGFloat] = [1, 3, 5, 13, 17, 20] 30 | 31 | // simple line with custom x axis labels 32 | let xLabels: [String] = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"] 33 | 34 | lineChart = LineChart() 35 | lineChart.animation.enabled = true 36 | lineChart.area = true 37 | lineChart.x.labels.visible = true 38 | lineChart.x.grid.count = 5 39 | lineChart.y.grid.count = 5 40 | lineChart.x.labels.values = xLabels 41 | lineChart.y.labels.visible = true 42 | lineChart.addLine(data) 43 | lineChart.addLine(data2) 44 | 45 | lineChart.translatesAutoresizingMaskIntoConstraints = false 46 | lineChart.delegate = self 47 | self.view.addSubview(lineChart) 48 | views["chart"] = lineChart 49 | view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[chart]-|", options: [], metrics: nil, views: views)) 50 | view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[label]-[chart(==200)]", options: [], metrics: nil, views: views)) 51 | 52 | // var delta: Int64 = 4 * Int64(NSEC_PER_SEC) 53 | // var time = dispatch_time(DISPATCH_TIME_NOW, delta) 54 | // 55 | // dispatch_after(time, dispatch_get_main_queue(), { 56 | // self.lineChart.clear() 57 | // self.lineChart.addLine(data2) 58 | // }); 59 | 60 | // var scale = LinearScale(domain: [0, 100], range: [0.0, 100.0]) 61 | // var linear = scale.scale() 62 | // var invert = scale.invert() 63 | // println(linear(x: 2.5)) // 50 64 | // println(invert(x: 50)) // 2.5 65 | 66 | } 67 | 68 | 69 | 70 | override func didReceiveMemoryWarning() { 71 | super.didReceiveMemoryWarning() 72 | } 73 | 74 | 75 | 76 | /** 77 | * Line chart delegate method. 78 | */ 79 | func didSelectDataPoint(_ x: CGFloat, yValues: Array) { 80 | label.text = "x: \(x) y: \(yValues)" 81 | } 82 | 83 | 84 | 85 | /** 86 | * Redraw chart on device rotation. 87 | */ 88 | override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) { 89 | if let chart = lineChart { 90 | chart.setNeedsDisplay() 91 | } 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /linechartTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /linechartTests/linechartTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // linechartTests.swift 3 | // linechartTests 4 | // 5 | // Created by Zeiss, Mirco on 01.07.14. 6 | // Copyright (c) 2014 zemirco. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class linechartTests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | super.tearDown() 21 | } 22 | 23 | func testExample() { 24 | // This is an example of a functional test case. 25 | XCTAssert(true, "Pass") 26 | } 27 | 28 | func testPerformanceExample() { 29 | // This is an example of a performance test case. 30 | self.measure() { 31 | // Put the code you want to measure the time of here. 32 | } 33 | } 34 | 35 | } 36 | --------------------------------------------------------------------------------