├── LICENSE ├── README.md ├── TestSwiftCharts.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── jknlsn.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── TestSwiftCharts ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Chart.swift ├── ContentView.swift ├── Data.swift ├── MinimalChart.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── TestSwiftCharts.entitlements └── TestSwiftChartsApp.swift └── screenshot.jpg /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jake Nelson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MultipleDataSetSwiftChartsExample 2 | Minimal example of Swift Chart showing multiple data sets with multiple axis in iOS 16. 3 | 4 | Prompted by trying to show weather data on a single chart with unrelated axis, i.e. wind speed and pressure. 5 | 6 | ![Screenshot of graph showing pressure and wind speed on the same chart](https://raw.githubusercontent.com/jknlsn/MultipleDataSetSwiftChartsExample/main/screenshot.jpg) 7 | -------------------------------------------------------------------------------- /TestSwiftCharts.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C245519628906927005F0089 /* TestSwiftChartsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C245519528906927005F0089 /* TestSwiftChartsApp.swift */; }; 11 | C245519828906927005F0089 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C245519728906927005F0089 /* ContentView.swift */; }; 12 | C245519A28906929005F0089 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C245519928906929005F0089 /* Assets.xcassets */; }; 13 | C245519E28906929005F0089 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C245519D28906929005F0089 /* Preview Assets.xcassets */; }; 14 | C24551A5289092AC005F0089 /* MinimalChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = C24551A4289092AC005F0089 /* MinimalChart.swift */; }; 15 | C24551A7289112CD005F0089 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = C24551A6289112CD005F0089 /* Data.swift */; }; 16 | C24551A9289112EF005F0089 /* Chart.swift in Sources */ = {isa = PBXBuildFile; fileRef = C24551A8289112EF005F0089 /* Chart.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | C245519228906927005F0089 /* TestSwiftCharts.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestSwiftCharts.app; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | C245519528906927005F0089 /* TestSwiftChartsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSwiftChartsApp.swift; sourceTree = ""; }; 22 | C245519728906927005F0089 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 23 | C245519928906929005F0089 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 24 | C245519B28906929005F0089 /* TestSwiftCharts.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TestSwiftCharts.entitlements; sourceTree = ""; }; 25 | C245519D28906929005F0089 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 26 | C24551A4289092AC005F0089 /* MinimalChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinimalChart.swift; sourceTree = ""; }; 27 | C24551A6289112CD005F0089 /* Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = ""; }; 28 | C24551A8289112EF005F0089 /* Chart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Chart.swift; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | C245518F28906927005F0089 /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | C245518928906927005F0089 = { 43 | isa = PBXGroup; 44 | children = ( 45 | C245519428906927005F0089 /* TestSwiftCharts */, 46 | C245519328906927005F0089 /* Products */, 47 | ); 48 | sourceTree = ""; 49 | }; 50 | C245519328906927005F0089 /* Products */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | C245519228906927005F0089 /* TestSwiftCharts.app */, 54 | ); 55 | name = Products; 56 | sourceTree = ""; 57 | }; 58 | C245519428906927005F0089 /* TestSwiftCharts */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | C245519528906927005F0089 /* TestSwiftChartsApp.swift */, 62 | C245519728906927005F0089 /* ContentView.swift */, 63 | C24551A8289112EF005F0089 /* Chart.swift */, 64 | C24551A4289092AC005F0089 /* MinimalChart.swift */, 65 | C245519928906929005F0089 /* Assets.xcassets */, 66 | C245519B28906929005F0089 /* TestSwiftCharts.entitlements */, 67 | C245519C28906929005F0089 /* Preview Content */, 68 | C24551A6289112CD005F0089 /* Data.swift */, 69 | ); 70 | path = TestSwiftCharts; 71 | sourceTree = ""; 72 | }; 73 | C245519C28906929005F0089 /* Preview Content */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | C245519D28906929005F0089 /* Preview Assets.xcassets */, 77 | ); 78 | path = "Preview Content"; 79 | sourceTree = ""; 80 | }; 81 | /* End PBXGroup section */ 82 | 83 | /* Begin PBXNativeTarget section */ 84 | C245519128906927005F0089 /* TestSwiftCharts */ = { 85 | isa = PBXNativeTarget; 86 | buildConfigurationList = C24551A128906929005F0089 /* Build configuration list for PBXNativeTarget "TestSwiftCharts" */; 87 | buildPhases = ( 88 | C245518E28906927005F0089 /* Sources */, 89 | C245518F28906927005F0089 /* Frameworks */, 90 | C245519028906927005F0089 /* Resources */, 91 | ); 92 | buildRules = ( 93 | ); 94 | dependencies = ( 95 | ); 96 | name = TestSwiftCharts; 97 | productName = TestSwiftCharts; 98 | productReference = C245519228906927005F0089 /* TestSwiftCharts.app */; 99 | productType = "com.apple.product-type.application"; 100 | }; 101 | /* End PBXNativeTarget section */ 102 | 103 | /* Begin PBXProject section */ 104 | C245518A28906927005F0089 /* Project object */ = { 105 | isa = PBXProject; 106 | attributes = { 107 | BuildIndependentTargetsInParallel = 1; 108 | LastSwiftUpdateCheck = 1400; 109 | LastUpgradeCheck = 1400; 110 | TargetAttributes = { 111 | C245519128906927005F0089 = { 112 | CreatedOnToolsVersion = 14.0; 113 | }; 114 | }; 115 | }; 116 | buildConfigurationList = C245518D28906927005F0089 /* Build configuration list for PBXProject "TestSwiftCharts" */; 117 | compatibilityVersion = "Xcode 14.0"; 118 | developmentRegion = en; 119 | hasScannedForEncodings = 0; 120 | knownRegions = ( 121 | en, 122 | Base, 123 | ); 124 | mainGroup = C245518928906927005F0089; 125 | productRefGroup = C245519328906927005F0089 /* Products */; 126 | projectDirPath = ""; 127 | projectRoot = ""; 128 | targets = ( 129 | C245519128906927005F0089 /* TestSwiftCharts */, 130 | ); 131 | }; 132 | /* End PBXProject section */ 133 | 134 | /* Begin PBXResourcesBuildPhase section */ 135 | C245519028906927005F0089 /* Resources */ = { 136 | isa = PBXResourcesBuildPhase; 137 | buildActionMask = 2147483647; 138 | files = ( 139 | C245519E28906929005F0089 /* Preview Assets.xcassets in Resources */, 140 | C245519A28906929005F0089 /* Assets.xcassets in Resources */, 141 | ); 142 | runOnlyForDeploymentPostprocessing = 0; 143 | }; 144 | /* End PBXResourcesBuildPhase section */ 145 | 146 | /* Begin PBXSourcesBuildPhase section */ 147 | C245518E28906927005F0089 /* Sources */ = { 148 | isa = PBXSourcesBuildPhase; 149 | buildActionMask = 2147483647; 150 | files = ( 151 | C24551A9289112EF005F0089 /* Chart.swift in Sources */, 152 | C24551A5289092AC005F0089 /* MinimalChart.swift in Sources */, 153 | C245519828906927005F0089 /* ContentView.swift in Sources */, 154 | C245519628906927005F0089 /* TestSwiftChartsApp.swift in Sources */, 155 | C24551A7289112CD005F0089 /* Data.swift in Sources */, 156 | ); 157 | runOnlyForDeploymentPostprocessing = 0; 158 | }; 159 | /* End PBXSourcesBuildPhase section */ 160 | 161 | /* Begin XCBuildConfiguration section */ 162 | C245519F28906929005F0089 /* Debug */ = { 163 | isa = XCBuildConfiguration; 164 | buildSettings = { 165 | ALWAYS_SEARCH_USER_PATHS = NO; 166 | CLANG_ANALYZER_NONNULL = YES; 167 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 168 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 169 | CLANG_ENABLE_MODULES = YES; 170 | CLANG_ENABLE_OBJC_ARC = YES; 171 | CLANG_ENABLE_OBJC_WEAK = YES; 172 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 173 | CLANG_WARN_BOOL_CONVERSION = YES; 174 | CLANG_WARN_COMMA = YES; 175 | CLANG_WARN_CONSTANT_CONVERSION = YES; 176 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 177 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 178 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 179 | CLANG_WARN_EMPTY_BODY = YES; 180 | CLANG_WARN_ENUM_CONVERSION = YES; 181 | CLANG_WARN_INFINITE_RECURSION = YES; 182 | CLANG_WARN_INT_CONVERSION = YES; 183 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 184 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 185 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 186 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 187 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 188 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 189 | CLANG_WARN_STRICT_PROTOTYPES = YES; 190 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 191 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 192 | CLANG_WARN_UNREACHABLE_CODE = YES; 193 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 194 | COPY_PHASE_STRIP = NO; 195 | DEBUG_INFORMATION_FORMAT = dwarf; 196 | ENABLE_STRICT_OBJC_MSGSEND = YES; 197 | ENABLE_TESTABILITY = YES; 198 | GCC_C_LANGUAGE_STANDARD = gnu11; 199 | GCC_DYNAMIC_NO_PIC = NO; 200 | GCC_NO_COMMON_BLOCKS = YES; 201 | GCC_OPTIMIZATION_LEVEL = 0; 202 | GCC_PREPROCESSOR_DEFINITIONS = ( 203 | "DEBUG=1", 204 | "$(inherited)", 205 | ); 206 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 207 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 208 | GCC_WARN_UNDECLARED_SELECTOR = YES; 209 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 210 | GCC_WARN_UNUSED_FUNCTION = YES; 211 | GCC_WARN_UNUSED_VARIABLE = YES; 212 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 213 | MTL_FAST_MATH = YES; 214 | ONLY_ACTIVE_ARCH = YES; 215 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 216 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 217 | }; 218 | name = Debug; 219 | }; 220 | C24551A028906929005F0089 /* Release */ = { 221 | isa = XCBuildConfiguration; 222 | buildSettings = { 223 | ALWAYS_SEARCH_USER_PATHS = NO; 224 | CLANG_ANALYZER_NONNULL = YES; 225 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 226 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 227 | CLANG_ENABLE_MODULES = YES; 228 | CLANG_ENABLE_OBJC_ARC = YES; 229 | CLANG_ENABLE_OBJC_WEAK = YES; 230 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 231 | CLANG_WARN_BOOL_CONVERSION = YES; 232 | CLANG_WARN_COMMA = YES; 233 | CLANG_WARN_CONSTANT_CONVERSION = YES; 234 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 235 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 236 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 237 | CLANG_WARN_EMPTY_BODY = YES; 238 | CLANG_WARN_ENUM_CONVERSION = YES; 239 | CLANG_WARN_INFINITE_RECURSION = YES; 240 | CLANG_WARN_INT_CONVERSION = YES; 241 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 242 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 243 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 244 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 245 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 246 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 247 | CLANG_WARN_STRICT_PROTOTYPES = YES; 248 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 249 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 250 | CLANG_WARN_UNREACHABLE_CODE = YES; 251 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 252 | COPY_PHASE_STRIP = NO; 253 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 254 | ENABLE_NS_ASSERTIONS = NO; 255 | ENABLE_STRICT_OBJC_MSGSEND = YES; 256 | GCC_C_LANGUAGE_STANDARD = gnu11; 257 | GCC_NO_COMMON_BLOCKS = YES; 258 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 259 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 260 | GCC_WARN_UNDECLARED_SELECTOR = YES; 261 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 262 | GCC_WARN_UNUSED_FUNCTION = YES; 263 | GCC_WARN_UNUSED_VARIABLE = YES; 264 | MTL_ENABLE_DEBUG_INFO = NO; 265 | MTL_FAST_MATH = YES; 266 | SWIFT_COMPILATION_MODE = wholemodule; 267 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 268 | }; 269 | name = Release; 270 | }; 271 | C24551A228906929005F0089 /* Debug */ = { 272 | isa = XCBuildConfiguration; 273 | buildSettings = { 274 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 275 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 276 | CODE_SIGN_ENTITLEMENTS = TestSwiftCharts/TestSwiftCharts.entitlements; 277 | CODE_SIGN_STYLE = Automatic; 278 | CURRENT_PROJECT_VERSION = 1; 279 | DEVELOPMENT_ASSET_PATHS = "\"TestSwiftCharts/Preview Content\""; 280 | DEVELOPMENT_TEAM = FL3R79336R; 281 | ENABLE_HARDENED_RUNTIME = YES; 282 | ENABLE_PREVIEWS = YES; 283 | GENERATE_INFOPLIST_FILE = YES; 284 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 285 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 286 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 287 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 288 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 289 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 290 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 291 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 292 | IPHONEOS_DEPLOYMENT_TARGET = 16.0; 293 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 294 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 295 | MACOSX_DEPLOYMENT_TARGET = 12.4; 296 | MARKETING_VERSION = 1.0; 297 | PRODUCT_BUNDLE_IDENTIFIER = com.smallcolossus.TestSwiftCharts; 298 | PRODUCT_NAME = "$(TARGET_NAME)"; 299 | SDKROOT = auto; 300 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 301 | SWIFT_EMIT_LOC_STRINGS = YES; 302 | SWIFT_VERSION = 5.0; 303 | TARGETED_DEVICE_FAMILY = "1,2"; 304 | }; 305 | name = Debug; 306 | }; 307 | C24551A328906929005F0089 /* Release */ = { 308 | isa = XCBuildConfiguration; 309 | buildSettings = { 310 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 311 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 312 | CODE_SIGN_ENTITLEMENTS = TestSwiftCharts/TestSwiftCharts.entitlements; 313 | CODE_SIGN_STYLE = Automatic; 314 | CURRENT_PROJECT_VERSION = 1; 315 | DEVELOPMENT_ASSET_PATHS = "\"TestSwiftCharts/Preview Content\""; 316 | DEVELOPMENT_TEAM = FL3R79336R; 317 | ENABLE_HARDENED_RUNTIME = YES; 318 | ENABLE_PREVIEWS = YES; 319 | GENERATE_INFOPLIST_FILE = YES; 320 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 321 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 322 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 323 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 324 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 325 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 326 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 327 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 328 | IPHONEOS_DEPLOYMENT_TARGET = 16.0; 329 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 330 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 331 | MACOSX_DEPLOYMENT_TARGET = 12.4; 332 | MARKETING_VERSION = 1.0; 333 | PRODUCT_BUNDLE_IDENTIFIER = com.smallcolossus.TestSwiftCharts; 334 | PRODUCT_NAME = "$(TARGET_NAME)"; 335 | SDKROOT = auto; 336 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 337 | SWIFT_EMIT_LOC_STRINGS = YES; 338 | SWIFT_VERSION = 5.0; 339 | TARGETED_DEVICE_FAMILY = "1,2"; 340 | }; 341 | name = Release; 342 | }; 343 | /* End XCBuildConfiguration section */ 344 | 345 | /* Begin XCConfigurationList section */ 346 | C245518D28906927005F0089 /* Build configuration list for PBXProject "TestSwiftCharts" */ = { 347 | isa = XCConfigurationList; 348 | buildConfigurations = ( 349 | C245519F28906929005F0089 /* Debug */, 350 | C24551A028906929005F0089 /* Release */, 351 | ); 352 | defaultConfigurationIsVisible = 0; 353 | defaultConfigurationName = Release; 354 | }; 355 | C24551A128906929005F0089 /* Build configuration list for PBXNativeTarget "TestSwiftCharts" */ = { 356 | isa = XCConfigurationList; 357 | buildConfigurations = ( 358 | C24551A228906929005F0089 /* Debug */, 359 | C24551A328906929005F0089 /* Release */, 360 | ); 361 | defaultConfigurationIsVisible = 0; 362 | defaultConfigurationName = Release; 363 | }; 364 | /* End XCConfigurationList section */ 365 | }; 366 | rootObject = C245518A28906927005F0089 /* Project object */; 367 | } 368 | -------------------------------------------------------------------------------- /TestSwiftCharts.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TestSwiftCharts.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TestSwiftCharts.xcodeproj/xcuserdata/jknlsn.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TestSwiftCharts.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /TestSwiftCharts/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /TestSwiftCharts/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "1x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "2x", 16 | "size" : "16x16" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "1x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "2x", 26 | "size" : "32x32" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "2x", 36 | "size" : "128x128" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "1x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "2x", 46 | "size" : "256x256" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "1x", 51 | "size" : "512x512" 52 | }, 53 | { 54 | "idiom" : "mac", 55 | "scale" : "2x", 56 | "size" : "512x512" 57 | } 58 | ], 59 | "info" : { 60 | "author" : "xcode", 61 | "version" : 1 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /TestSwiftCharts/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /TestSwiftCharts/Chart.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Chart.swift 3 | // TestSwiftCharts 4 | // 5 | // Created by Jake Nelson on 27/07/2022. 6 | // 7 | 8 | import SwiftUI 9 | import Charts 10 | 11 | 12 | struct InteractiveLollipopChart: View { 13 | 14 | @Binding var selectedElement: HourWeatherStruct? 15 | 16 | func findElement(location: CGPoint, proxy: ChartProxy, geometry: GeometryProxy) -> HourWeatherStruct? { 17 | let relativeXPosition = location.x - geometry[proxy.plotAreaFrame].origin.x 18 | if let date = proxy.value(atX: relativeXPosition) as Date? { 19 | // Find the closest date element. 20 | var minDistance: TimeInterval = .infinity 21 | var index: Int? = nil 22 | 23 | for salesDataIndex in hours.indices { 24 | let nthSalesDataDistance = hours[salesDataIndex].date.distance(to: date) 25 | if abs(nthSalesDataDistance) < minDistance { 26 | minDistance = abs(nthSalesDataDistance) 27 | index = salesDataIndex 28 | } 29 | } 30 | if let index = index { 31 | return hours[index] 32 | } 33 | } 34 | return nil 35 | } 36 | 37 | let colors: [Color] = [.red, .green, .purple, .teal, .orange] 38 | var randomGradient: AnyGradient { 39 | if let color = colors.randomElement() { 40 | return color.gradient 41 | } 42 | else { 43 | return Color.red.gradient 44 | } 45 | } 46 | 47 | var testGradient: LinearGradient { 48 | return LinearGradient(colors: [.yellow.opacity(0.6), .blue.opacity(0.6)], startPoint: .top, endPoint: .bottom) 49 | } 50 | 51 | var body: some View { 52 | Chart { 53 | ForEach(hours, id: \.date) { 54 | LineMark( 55 | x: .value("Date", $0.date, unit: .hour), 56 | y: .value("Wind Speed", $0.windSpeed) 57 | ) 58 | .foregroundStyle(by: .value("Value", "Wind")) 59 | 60 | LineMark( 61 | x: .value("Date", $0.date, unit: .hour), 62 | y: .value("Pressure", ($0.pressure - 1014) * 4) 63 | ) 64 | .foregroundStyle(by: .value("Value", "Pressure")) 65 | } 66 | .lineStyle(StrokeStyle(lineWidth: 4.0)) 67 | .interpolationMethod(.catmullRom) 68 | 69 | if let selectedElement = selectedElement { 70 | PointMark( 71 | x: .value("Date", selectedElement.date, unit: .hour), 72 | y: .value("Pressure", (selectedElement.pressure - 1014) * 4) 73 | ) 74 | .symbolSize(100.0) 75 | .foregroundStyle( 76 | Color.purple 77 | ) 78 | PointMark( 79 | x: .value("Date", selectedElement.date, unit: .hour), 80 | y: .value("Wind Speed", selectedElement.windSpeed) 81 | ) 82 | .symbolSize(100.0) 83 | .foregroundStyle( 84 | Color.teal 85 | ) 86 | } 87 | } 88 | .chartForegroundStyleScale([ 89 | "Pressure": .purple, 90 | "Wind": .teal 91 | ]) 92 | .chartXAxis { 93 | // AxisMarks(position: .top, values: .stride(by: .hour, count: 2)) { 94 | // _ in 95 | // AxisValueLabel("🌊", centered: true) 96 | // } 97 | AxisMarks(position: .bottom, values: .stride(by: .hour, count: 2)) { 98 | _ in 99 | AxisTick() 100 | AxisGridLine() 101 | AxisValueLabel(format: .dateTime.hour(), centered: true) 102 | } 103 | } 104 | .chartYAxis { 105 | AxisMarks(position: .leading, values: Array(stride(from: 0, through: 24, by: 4))){ 106 | axis in 107 | AxisTick() 108 | AxisGridLine() 109 | AxisValueLabel("\(1014 + (axis.index * 1))", centered: false) 110 | } 111 | AxisMarks(position: .trailing, values: Array(stride(from: 0, through: 24, by: 4))){ 112 | axis in 113 | AxisTick() 114 | AxisGridLine() 115 | AxisValueLabel("\(axis.index * 4)", centered: false) 116 | } 117 | } 118 | .chartOverlay { proxy in 119 | GeometryReader { nthGeometryItem in 120 | Rectangle().fill(.clear).contentShape(Rectangle()) 121 | .gesture( 122 | SpatialTapGesture() 123 | .onEnded { value in 124 | let element = findElement(location: value.location, proxy: proxy, geometry: nthGeometryItem) 125 | if selectedElement?.date == element?.date { 126 | // If tapping the same element, clear the selection. 127 | selectedElement = nil 128 | } else { 129 | selectedElement = element 130 | } 131 | } 132 | .exclusively( 133 | before: DragGesture() 134 | .onChanged { value in 135 | selectedElement = findElement(location: value.location, proxy: proxy, geometry: nthGeometryItem) 136 | } 137 | ) 138 | ) 139 | } 140 | } 141 | } 142 | } 143 | 144 | struct InteractiveLollipop: View { 145 | 146 | @State private var selectedElement: HourWeatherStruct? = nil 147 | @Environment(\.layoutDirection) var layoutDirection 148 | 149 | var body: some View { 150 | List { 151 | VStack(alignment: .leading) { 152 | VStack(alignment: .leading) { 153 | Text("Windspeed and Pressure") 154 | .font(.callout) 155 | .foregroundStyle(.secondary) 156 | Text("\(hours.first?.date ?? Date(), format: .dateTime)") 157 | .font(.title2.bold()) 158 | } 159 | .opacity(selectedElement == nil ? 1 : 0) 160 | 161 | InteractiveLollipopChart(selectedElement: $selectedElement) 162 | .frame(height: 200) 163 | } 164 | .chartBackground { proxy in 165 | ZStack(alignment: .topLeading) { 166 | GeometryReader { nthGeoItem in 167 | if let selectedElement = selectedElement { 168 | let dateInterval = Calendar.current.dateInterval(of: .hour, for: selectedElement.date)! 169 | let startPositionX1 = proxy.position(forX: dateInterval.start) ?? 0 170 | let startPositionX2 = proxy.position(forX: dateInterval.end) ?? 0 171 | let midStartPositionX = (startPositionX1 + startPositionX2) / 2 + nthGeoItem[proxy.plotAreaFrame].origin.x 172 | 173 | let lineX = layoutDirection == .rightToLeft ? nthGeoItem.size.width - midStartPositionX : midStartPositionX 174 | let lineHeight = nthGeoItem[proxy.plotAreaFrame].maxY 175 | let boxWidth: CGFloat = 150 176 | let boxOffset = max(0, min(nthGeoItem.size.width - boxWidth, lineX - boxWidth / 2)) 177 | 178 | Rectangle() 179 | .fill(.quaternary) 180 | .frame(width: 2, height: lineHeight) 181 | .position(x: lineX, y: lineHeight / 2) 182 | 183 | VStack(alignment: .leading) { 184 | Text("\(selectedElement.date, format: .dateTime.hour())") 185 | .font(.callout) 186 | .foregroundStyle(.secondary) 187 | Text("\(selectedElement.windSpeed, format: .number)km/h\n\(selectedElement.pressure, format: .number)kpa") 188 | .font(.body.bold()) 189 | .foregroundColor(.primary) 190 | } 191 | .frame(width: boxWidth, alignment: .leading) 192 | .background { 193 | ZStack { 194 | RoundedRectangle(cornerRadius: 8) 195 | .fill(.background) 196 | RoundedRectangle(cornerRadius: 8) 197 | .fill(.quaternary.opacity(0.7)) 198 | } 199 | .padding([.leading, .trailing], -8) 200 | .padding([.top, .bottom], -4) 201 | } 202 | .offset(x: boxOffset) 203 | } 204 | } 205 | } 206 | } 207 | .listRowSeparator(.hidden) 208 | } 209 | .listStyle(.plain) 210 | .navigationBarTitle("Interactive Lollipop", displayMode: .inline) 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /TestSwiftCharts/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // TestSwiftCharts 4 | // 5 | // Created by Jake Nelson on 26/07/2022. 6 | // 7 | 8 | import Charts 9 | import SwiftUI 10 | import WeatherKit 11 | 12 | struct ContentView: View { 13 | var body: some View { 14 | InteractiveLollipop() 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /TestSwiftCharts/Data.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Data.swift 3 | // TestSwiftCharts 4 | // 5 | // Created by Jake Nelson on 27/07/2022. 6 | // 7 | 8 | import Foundation 9 | 10 | 11 | struct HourWeatherStruct { 12 | var date: Date 13 | var pressure: Double 14 | var temperature: Double 15 | var windSpeed: Double 16 | } 17 | 18 | let hours: [HourWeatherStruct] = [ 19 | HourWeatherStruct(date: Date(timeIntervalSinceNow: 3600), 20 | pressure: 1015.0, 21 | temperature: 18.2, 22 | windSpeed: 6.1), 23 | HourWeatherStruct(date: Date(timeIntervalSinceNow: 3600 * 2), 24 | pressure: 1015.3, 25 | temperature: 18.2, 26 | windSpeed: 8.1), 27 | HourWeatherStruct(date: Date(timeIntervalSinceNow: 3600 * 3), 28 | pressure: 1015.9, 29 | temperature: 18.2, 30 | windSpeed: 9.4), 31 | HourWeatherStruct(date: Date(timeIntervalSinceNow: 3600 * 4), 32 | pressure: 1016.3, 33 | temperature: 18.2, 34 | windSpeed: 5.2), 35 | HourWeatherStruct(date: Date(timeIntervalSinceNow: 3600 * 5), 36 | pressure: 1016.3, 37 | temperature: 18.2, 38 | windSpeed: 12.1), 39 | HourWeatherStruct(date: Date(timeIntervalSinceNow: 3600 * 6), 40 | pressure: 1016.3, 41 | temperature: 18.2, 42 | windSpeed: 11.1), 43 | HourWeatherStruct(date: Date(timeIntervalSinceNow: 3600 * 7), 44 | pressure: 1017.3, 45 | temperature: 18.2, 46 | windSpeed: 10.1), 47 | HourWeatherStruct(date: Date(timeIntervalSinceNow: 3600 * 8), 48 | pressure: 1018.3, 49 | temperature: 18.2, 50 | windSpeed: 11.1), 51 | HourWeatherStruct(date: Date(timeIntervalSinceNow: 3600 * 9), 52 | pressure: 1018.3, 53 | temperature: 18.2, 54 | windSpeed: 9.1), 55 | HourWeatherStruct(date: Date(timeIntervalSinceNow: 3600 * 10), 56 | pressure: 1018.3, 57 | temperature: 18.2, 58 | windSpeed: 8.1), 59 | HourWeatherStruct(date: Date(timeIntervalSinceNow: 3600 * 11), 60 | pressure: 1017.3, 61 | temperature: 18.2, 62 | windSpeed: 19.9), 63 | HourWeatherStruct(date: Date(timeIntervalSinceNow: 3600 * 12), 64 | pressure: 1018.3, 65 | temperature: 18.2, 66 | windSpeed: 7.1), 67 | ] 68 | -------------------------------------------------------------------------------- /TestSwiftCharts/MinimalChart.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MinimalChart.swift 3 | // TestSwiftCharts 4 | // 5 | // Created by Jake Nelson on 26/07/2022. 6 | // 7 | 8 | import Charts 9 | import SwiftUI 10 | import WeatherKit 11 | 12 | struct InteractiveLollipopChartMinimal: View { 13 | 14 | var body: some View { 15 | Chart { 16 | ForEach(hours, id: \.date) { 17 | LineMark( 18 | x: .value("Date", $0.date, unit: .hour), 19 | y: .value("Wind Speed", $0.windSpeed) 20 | ) 21 | .foregroundStyle(by: .value("Value", "Wind")) 22 | 23 | LineMark( 24 | x: .value("Date", $0.date, unit: .hour), 25 | y: .value("Pressure", ($0.pressure - 1014) * 4) 26 | ) 27 | .foregroundStyle(by: .value("Value", "Pressure")) 28 | } 29 | .lineStyle(StrokeStyle(lineWidth: 4.0)) 30 | .interpolationMethod(.catmullRom) 31 | } 32 | .chartForegroundStyleScale([ 33 | "Pressure": .purple, 34 | "Wind": .teal 35 | ]) 36 | .chartXAxis { 37 | AxisMarks(position: .bottom, values: .stride(by: .hour, count: 2)) { 38 | _ in 39 | AxisTick() 40 | AxisGridLine() 41 | AxisValueLabel(format: .dateTime.hour(), centered: true) 42 | } 43 | } 44 | .chartYAxis { 45 | AxisMarks(position: .leading, values: Array(stride(from: 0, through: 24, by: 4))){ 46 | axis in 47 | AxisTick() 48 | AxisGridLine() 49 | AxisValueLabel("\(1014 + (axis.index * 1))", centered: false) 50 | } 51 | AxisMarks(position: .trailing, values: Array(stride(from: 0, through: 24, by: 4))){ 52 | axis in 53 | AxisTick() 54 | AxisGridLine() 55 | AxisValueLabel("\(axis.index * 4)", centered: false) 56 | } 57 | } 58 | } 59 | } 60 | 61 | struct InteractiveLollipopMinimal: View { 62 | 63 | var body: some View { 64 | List { 65 | VStack(alignment: .leading) { 66 | VStack(alignment: .leading) { 67 | Text("Windspeed and Pressure") 68 | .font(.callout) 69 | .foregroundStyle(.secondary) 70 | Text("\(hours.first?.date ?? Date(), format: .dateTime)") 71 | .font(.title2.bold()) 72 | } 73 | 74 | InteractiveLollipopChartMinimal() 75 | .frame(height: 200) 76 | } 77 | .listRowSeparator(.hidden) 78 | } 79 | .listStyle(.plain) 80 | .navigationBarTitle("Interactive Lollipop", displayMode: .inline) 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /TestSwiftCharts/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /TestSwiftCharts/TestSwiftCharts.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /TestSwiftCharts/TestSwiftChartsApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestSwiftChartsApp.swift 3 | // TestSwiftCharts 4 | // 5 | // Created by Jake Nelson on 26/07/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct TestSwiftChartsApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jknlsn/MultipleDataSetSwiftChartsExample/f3c3b2e253ad9dac84d00ddadfb864a765dd3bf4/screenshot.jpg --------------------------------------------------------------------------------