├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── DemoApp └── DemoApp │ ├── DemoApp.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ ├── DemoApp │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── ContentView.swift │ ├── DemoAppApp.swift │ └── Preview Content │ │ └── Preview Assets.xcassets │ │ └── Contents.json │ ├── DemoAppTests │ └── DemoAppTests.swift │ └── DemoAppUITests │ ├── DemoAppUITests.swift │ └── DemoAppUITestsLaunchTests.swift ├── Package.swift ├── README.md └── Sources └── ContributionChart ├── ContributionChartTest.swift └── ContributionChartView.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/config/registries.json 8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 9 | .netrc 10 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DemoApp/DemoApp/DemoApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C00592122C59D22F00FAE9E2 /* DemoAppApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C00592112C59D22F00FAE9E2 /* DemoAppApp.swift */; }; 11 | C00592142C59D22F00FAE9E2 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C00592132C59D22F00FAE9E2 /* ContentView.swift */; }; 12 | C00592162C59D23000FAE9E2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C00592152C59D23000FAE9E2 /* Assets.xcassets */; }; 13 | C00592192C59D23000FAE9E2 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C00592182C59D23000FAE9E2 /* Preview Assets.xcassets */; }; 14 | C00592232C59D23100FAE9E2 /* DemoAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C00592222C59D23100FAE9E2 /* DemoAppTests.swift */; }; 15 | C005922D2C59D23100FAE9E2 /* DemoAppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C005922C2C59D23100FAE9E2 /* DemoAppUITests.swift */; }; 16 | C005922F2C59D23100FAE9E2 /* DemoAppUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C005922E2C59D23100FAE9E2 /* DemoAppUITestsLaunchTests.swift */; }; 17 | C005923E2C59D5BC00FAE9E2 /* ContributionChart in Frameworks */ = {isa = PBXBuildFile; productRef = C005923D2C59D5BC00FAE9E2 /* ContributionChart */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXContainerItemProxy section */ 21 | C005921F2C59D23000FAE9E2 /* PBXContainerItemProxy */ = { 22 | isa = PBXContainerItemProxy; 23 | containerPortal = C00592062C59D22F00FAE9E2 /* Project object */; 24 | proxyType = 1; 25 | remoteGlobalIDString = C005920D2C59D22F00FAE9E2; 26 | remoteInfo = DemoApp; 27 | }; 28 | C00592292C59D23100FAE9E2 /* PBXContainerItemProxy */ = { 29 | isa = PBXContainerItemProxy; 30 | containerPortal = C00592062C59D22F00FAE9E2 /* Project object */; 31 | proxyType = 1; 32 | remoteGlobalIDString = C005920D2C59D22F00FAE9E2; 33 | remoteInfo = DemoApp; 34 | }; 35 | /* End PBXContainerItemProxy section */ 36 | 37 | /* Begin PBXFileReference section */ 38 | C005920E2C59D22F00FAE9E2 /* DemoApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DemoApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | C00592112C59D22F00FAE9E2 /* DemoAppApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoAppApp.swift; sourceTree = ""; }; 40 | C00592132C59D22F00FAE9E2 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 41 | C00592152C59D23000FAE9E2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 42 | C00592182C59D23000FAE9E2 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 43 | C005921E2C59D23000FAE9E2 /* DemoAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DemoAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | C00592222C59D23100FAE9E2 /* DemoAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoAppTests.swift; sourceTree = ""; }; 45 | C00592282C59D23100FAE9E2 /* DemoAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DemoAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | C005922C2C59D23100FAE9E2 /* DemoAppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoAppUITests.swift; sourceTree = ""; }; 47 | C005922E2C59D23100FAE9E2 /* DemoAppUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoAppUITestsLaunchTests.swift; sourceTree = ""; }; 48 | C005923C2C59D57200FAE9E2 /* SwiftUI-ContributionChart */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "SwiftUI-ContributionChart"; path = ../..; sourceTree = ""; }; 49 | /* End PBXFileReference section */ 50 | 51 | /* Begin PBXFrameworksBuildPhase section */ 52 | C005920B2C59D22F00FAE9E2 /* Frameworks */ = { 53 | isa = PBXFrameworksBuildPhase; 54 | buildActionMask = 2147483647; 55 | files = ( 56 | C005923E2C59D5BC00FAE9E2 /* ContributionChart in Frameworks */, 57 | ); 58 | runOnlyForDeploymentPostprocessing = 0; 59 | }; 60 | C005921B2C59D23000FAE9E2 /* Frameworks */ = { 61 | isa = PBXFrameworksBuildPhase; 62 | buildActionMask = 2147483647; 63 | files = ( 64 | ); 65 | runOnlyForDeploymentPostprocessing = 0; 66 | }; 67 | C00592252C59D23100FAE9E2 /* Frameworks */ = { 68 | isa = PBXFrameworksBuildPhase; 69 | buildActionMask = 2147483647; 70 | files = ( 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | /* End PBXFrameworksBuildPhase section */ 75 | 76 | /* Begin PBXGroup section */ 77 | C00592052C59D22F00FAE9E2 = { 78 | isa = PBXGroup; 79 | children = ( 80 | C00592102C59D22F00FAE9E2 /* DemoApp */, 81 | C00592212C59D23100FAE9E2 /* DemoAppTests */, 82 | C005922B2C59D23100FAE9E2 /* DemoAppUITests */, 83 | C005920F2C59D22F00FAE9E2 /* Products */, 84 | C005923B2C59D57200FAE9E2 /* Frameworks */, 85 | ); 86 | sourceTree = ""; 87 | }; 88 | C005920F2C59D22F00FAE9E2 /* Products */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | C005920E2C59D22F00FAE9E2 /* DemoApp.app */, 92 | C005921E2C59D23000FAE9E2 /* DemoAppTests.xctest */, 93 | C00592282C59D23100FAE9E2 /* DemoAppUITests.xctest */, 94 | ); 95 | name = Products; 96 | sourceTree = ""; 97 | }; 98 | C00592102C59D22F00FAE9E2 /* DemoApp */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | C00592112C59D22F00FAE9E2 /* DemoAppApp.swift */, 102 | C00592132C59D22F00FAE9E2 /* ContentView.swift */, 103 | C00592152C59D23000FAE9E2 /* Assets.xcassets */, 104 | C00592172C59D23000FAE9E2 /* Preview Content */, 105 | ); 106 | path = DemoApp; 107 | sourceTree = ""; 108 | }; 109 | C00592172C59D23000FAE9E2 /* Preview Content */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | C00592182C59D23000FAE9E2 /* Preview Assets.xcassets */, 113 | ); 114 | path = "Preview Content"; 115 | sourceTree = ""; 116 | }; 117 | C00592212C59D23100FAE9E2 /* DemoAppTests */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | C00592222C59D23100FAE9E2 /* DemoAppTests.swift */, 121 | ); 122 | path = DemoAppTests; 123 | sourceTree = ""; 124 | }; 125 | C005922B2C59D23100FAE9E2 /* DemoAppUITests */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | C005922C2C59D23100FAE9E2 /* DemoAppUITests.swift */, 129 | C005922E2C59D23100FAE9E2 /* DemoAppUITestsLaunchTests.swift */, 130 | ); 131 | path = DemoAppUITests; 132 | sourceTree = ""; 133 | }; 134 | C005923B2C59D57200FAE9E2 /* Frameworks */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | C005923C2C59D57200FAE9E2 /* SwiftUI-ContributionChart */, 138 | ); 139 | name = Frameworks; 140 | sourceTree = ""; 141 | }; 142 | /* End PBXGroup section */ 143 | 144 | /* Begin PBXNativeTarget section */ 145 | C005920D2C59D22F00FAE9E2 /* DemoApp */ = { 146 | isa = PBXNativeTarget; 147 | buildConfigurationList = C00592322C59D23100FAE9E2 /* Build configuration list for PBXNativeTarget "DemoApp" */; 148 | buildPhases = ( 149 | C005920A2C59D22F00FAE9E2 /* Sources */, 150 | C005920B2C59D22F00FAE9E2 /* Frameworks */, 151 | C005920C2C59D22F00FAE9E2 /* Resources */, 152 | ); 153 | buildRules = ( 154 | ); 155 | dependencies = ( 156 | ); 157 | name = DemoApp; 158 | packageProductDependencies = ( 159 | C005923D2C59D5BC00FAE9E2 /* ContributionChart */, 160 | ); 161 | productName = DemoApp; 162 | productReference = C005920E2C59D22F00FAE9E2 /* DemoApp.app */; 163 | productType = "com.apple.product-type.application"; 164 | }; 165 | C005921D2C59D23000FAE9E2 /* DemoAppTests */ = { 166 | isa = PBXNativeTarget; 167 | buildConfigurationList = C00592352C59D23100FAE9E2 /* Build configuration list for PBXNativeTarget "DemoAppTests" */; 168 | buildPhases = ( 169 | C005921A2C59D23000FAE9E2 /* Sources */, 170 | C005921B2C59D23000FAE9E2 /* Frameworks */, 171 | C005921C2C59D23000FAE9E2 /* Resources */, 172 | ); 173 | buildRules = ( 174 | ); 175 | dependencies = ( 176 | C00592202C59D23000FAE9E2 /* PBXTargetDependency */, 177 | ); 178 | name = DemoAppTests; 179 | productName = DemoAppTests; 180 | productReference = C005921E2C59D23000FAE9E2 /* DemoAppTests.xctest */; 181 | productType = "com.apple.product-type.bundle.unit-test"; 182 | }; 183 | C00592272C59D23100FAE9E2 /* DemoAppUITests */ = { 184 | isa = PBXNativeTarget; 185 | buildConfigurationList = C00592382C59D23100FAE9E2 /* Build configuration list for PBXNativeTarget "DemoAppUITests" */; 186 | buildPhases = ( 187 | C00592242C59D23100FAE9E2 /* Sources */, 188 | C00592252C59D23100FAE9E2 /* Frameworks */, 189 | C00592262C59D23100FAE9E2 /* Resources */, 190 | ); 191 | buildRules = ( 192 | ); 193 | dependencies = ( 194 | C005922A2C59D23100FAE9E2 /* PBXTargetDependency */, 195 | ); 196 | name = DemoAppUITests; 197 | productName = DemoAppUITests; 198 | productReference = C00592282C59D23100FAE9E2 /* DemoAppUITests.xctest */; 199 | productType = "com.apple.product-type.bundle.ui-testing"; 200 | }; 201 | /* End PBXNativeTarget section */ 202 | 203 | /* Begin PBXProject section */ 204 | C00592062C59D22F00FAE9E2 /* Project object */ = { 205 | isa = PBXProject; 206 | attributes = { 207 | BuildIndependentTargetsInParallel = 1; 208 | LastSwiftUpdateCheck = 1540; 209 | LastUpgradeCheck = 1540; 210 | TargetAttributes = { 211 | C005920D2C59D22F00FAE9E2 = { 212 | CreatedOnToolsVersion = 15.4; 213 | }; 214 | C005921D2C59D23000FAE9E2 = { 215 | CreatedOnToolsVersion = 15.4; 216 | TestTargetID = C005920D2C59D22F00FAE9E2; 217 | }; 218 | C00592272C59D23100FAE9E2 = { 219 | CreatedOnToolsVersion = 15.4; 220 | TestTargetID = C005920D2C59D22F00FAE9E2; 221 | }; 222 | }; 223 | }; 224 | buildConfigurationList = C00592092C59D22F00FAE9E2 /* Build configuration list for PBXProject "DemoApp" */; 225 | compatibilityVersion = "Xcode 14.0"; 226 | developmentRegion = en; 227 | hasScannedForEncodings = 0; 228 | knownRegions = ( 229 | en, 230 | Base, 231 | ); 232 | mainGroup = C00592052C59D22F00FAE9E2; 233 | productRefGroup = C005920F2C59D22F00FAE9E2 /* Products */; 234 | projectDirPath = ""; 235 | projectRoot = ""; 236 | targets = ( 237 | C005920D2C59D22F00FAE9E2 /* DemoApp */, 238 | C005921D2C59D23000FAE9E2 /* DemoAppTests */, 239 | C00592272C59D23100FAE9E2 /* DemoAppUITests */, 240 | ); 241 | }; 242 | /* End PBXProject section */ 243 | 244 | /* Begin PBXResourcesBuildPhase section */ 245 | C005920C2C59D22F00FAE9E2 /* Resources */ = { 246 | isa = PBXResourcesBuildPhase; 247 | buildActionMask = 2147483647; 248 | files = ( 249 | C00592192C59D23000FAE9E2 /* Preview Assets.xcassets in Resources */, 250 | C00592162C59D23000FAE9E2 /* Assets.xcassets in Resources */, 251 | ); 252 | runOnlyForDeploymentPostprocessing = 0; 253 | }; 254 | C005921C2C59D23000FAE9E2 /* Resources */ = { 255 | isa = PBXResourcesBuildPhase; 256 | buildActionMask = 2147483647; 257 | files = ( 258 | ); 259 | runOnlyForDeploymentPostprocessing = 0; 260 | }; 261 | C00592262C59D23100FAE9E2 /* Resources */ = { 262 | isa = PBXResourcesBuildPhase; 263 | buildActionMask = 2147483647; 264 | files = ( 265 | ); 266 | runOnlyForDeploymentPostprocessing = 0; 267 | }; 268 | /* End PBXResourcesBuildPhase section */ 269 | 270 | /* Begin PBXSourcesBuildPhase section */ 271 | C005920A2C59D22F00FAE9E2 /* Sources */ = { 272 | isa = PBXSourcesBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | C00592142C59D22F00FAE9E2 /* ContentView.swift in Sources */, 276 | C00592122C59D22F00FAE9E2 /* DemoAppApp.swift in Sources */, 277 | ); 278 | runOnlyForDeploymentPostprocessing = 0; 279 | }; 280 | C005921A2C59D23000FAE9E2 /* Sources */ = { 281 | isa = PBXSourcesBuildPhase; 282 | buildActionMask = 2147483647; 283 | files = ( 284 | C00592232C59D23100FAE9E2 /* DemoAppTests.swift in Sources */, 285 | ); 286 | runOnlyForDeploymentPostprocessing = 0; 287 | }; 288 | C00592242C59D23100FAE9E2 /* Sources */ = { 289 | isa = PBXSourcesBuildPhase; 290 | buildActionMask = 2147483647; 291 | files = ( 292 | C005922D2C59D23100FAE9E2 /* DemoAppUITests.swift in Sources */, 293 | C005922F2C59D23100FAE9E2 /* DemoAppUITestsLaunchTests.swift in Sources */, 294 | ); 295 | runOnlyForDeploymentPostprocessing = 0; 296 | }; 297 | /* End PBXSourcesBuildPhase section */ 298 | 299 | /* Begin PBXTargetDependency section */ 300 | C00592202C59D23000FAE9E2 /* PBXTargetDependency */ = { 301 | isa = PBXTargetDependency; 302 | target = C005920D2C59D22F00FAE9E2 /* DemoApp */; 303 | targetProxy = C005921F2C59D23000FAE9E2 /* PBXContainerItemProxy */; 304 | }; 305 | C005922A2C59D23100FAE9E2 /* PBXTargetDependency */ = { 306 | isa = PBXTargetDependency; 307 | target = C005920D2C59D22F00FAE9E2 /* DemoApp */; 308 | targetProxy = C00592292C59D23100FAE9E2 /* PBXContainerItemProxy */; 309 | }; 310 | /* End PBXTargetDependency section */ 311 | 312 | /* Begin XCBuildConfiguration section */ 313 | C00592302C59D23100FAE9E2 /* Debug */ = { 314 | isa = XCBuildConfiguration; 315 | buildSettings = { 316 | ALWAYS_SEARCH_USER_PATHS = NO; 317 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 318 | CLANG_ANALYZER_NONNULL = YES; 319 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 320 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 321 | CLANG_ENABLE_MODULES = YES; 322 | CLANG_ENABLE_OBJC_ARC = YES; 323 | CLANG_ENABLE_OBJC_WEAK = YES; 324 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 325 | CLANG_WARN_BOOL_CONVERSION = YES; 326 | CLANG_WARN_COMMA = YES; 327 | CLANG_WARN_CONSTANT_CONVERSION = YES; 328 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 329 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 330 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 331 | CLANG_WARN_EMPTY_BODY = YES; 332 | CLANG_WARN_ENUM_CONVERSION = YES; 333 | CLANG_WARN_INFINITE_RECURSION = YES; 334 | CLANG_WARN_INT_CONVERSION = YES; 335 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 336 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 337 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 338 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 339 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 340 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 341 | CLANG_WARN_STRICT_PROTOTYPES = YES; 342 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 343 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 344 | CLANG_WARN_UNREACHABLE_CODE = YES; 345 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 346 | COPY_PHASE_STRIP = NO; 347 | DEBUG_INFORMATION_FORMAT = dwarf; 348 | ENABLE_STRICT_OBJC_MSGSEND = YES; 349 | ENABLE_TESTABILITY = YES; 350 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 351 | GCC_C_LANGUAGE_STANDARD = gnu17; 352 | GCC_DYNAMIC_NO_PIC = NO; 353 | GCC_NO_COMMON_BLOCKS = YES; 354 | GCC_OPTIMIZATION_LEVEL = 0; 355 | GCC_PREPROCESSOR_DEFINITIONS = ( 356 | "DEBUG=1", 357 | "$(inherited)", 358 | ); 359 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 360 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 361 | GCC_WARN_UNDECLARED_SELECTOR = YES; 362 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 363 | GCC_WARN_UNUSED_FUNCTION = YES; 364 | GCC_WARN_UNUSED_VARIABLE = YES; 365 | IPHONEOS_DEPLOYMENT_TARGET = 17.5; 366 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 367 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 368 | MTL_FAST_MATH = YES; 369 | ONLY_ACTIVE_ARCH = YES; 370 | SDKROOT = iphoneos; 371 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 372 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 373 | }; 374 | name = Debug; 375 | }; 376 | C00592312C59D23100FAE9E2 /* Release */ = { 377 | isa = XCBuildConfiguration; 378 | buildSettings = { 379 | ALWAYS_SEARCH_USER_PATHS = NO; 380 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 381 | CLANG_ANALYZER_NONNULL = YES; 382 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 383 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 384 | CLANG_ENABLE_MODULES = YES; 385 | CLANG_ENABLE_OBJC_ARC = YES; 386 | CLANG_ENABLE_OBJC_WEAK = YES; 387 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 388 | CLANG_WARN_BOOL_CONVERSION = YES; 389 | CLANG_WARN_COMMA = YES; 390 | CLANG_WARN_CONSTANT_CONVERSION = YES; 391 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 392 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 393 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 394 | CLANG_WARN_EMPTY_BODY = YES; 395 | CLANG_WARN_ENUM_CONVERSION = YES; 396 | CLANG_WARN_INFINITE_RECURSION = YES; 397 | CLANG_WARN_INT_CONVERSION = YES; 398 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 399 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 400 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 401 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 402 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 403 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 404 | CLANG_WARN_STRICT_PROTOTYPES = YES; 405 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 406 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 407 | CLANG_WARN_UNREACHABLE_CODE = YES; 408 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 409 | COPY_PHASE_STRIP = NO; 410 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 411 | ENABLE_NS_ASSERTIONS = NO; 412 | ENABLE_STRICT_OBJC_MSGSEND = YES; 413 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 414 | GCC_C_LANGUAGE_STANDARD = gnu17; 415 | GCC_NO_COMMON_BLOCKS = YES; 416 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 417 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 418 | GCC_WARN_UNDECLARED_SELECTOR = YES; 419 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 420 | GCC_WARN_UNUSED_FUNCTION = YES; 421 | GCC_WARN_UNUSED_VARIABLE = YES; 422 | IPHONEOS_DEPLOYMENT_TARGET = 17.5; 423 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 424 | MTL_ENABLE_DEBUG_INFO = NO; 425 | MTL_FAST_MATH = YES; 426 | SDKROOT = iphoneos; 427 | SWIFT_COMPILATION_MODE = wholemodule; 428 | VALIDATE_PRODUCT = YES; 429 | }; 430 | name = Release; 431 | }; 432 | C00592332C59D23100FAE9E2 /* Debug */ = { 433 | isa = XCBuildConfiguration; 434 | buildSettings = { 435 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 436 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 437 | CODE_SIGN_STYLE = Automatic; 438 | CURRENT_PROJECT_VERSION = 1; 439 | DEVELOPMENT_ASSET_PATHS = "\"DemoApp/Preview Content\""; 440 | ENABLE_PREVIEWS = YES; 441 | GENERATE_INFOPLIST_FILE = YES; 442 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 443 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 444 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 445 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 446 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 447 | LD_RUNPATH_SEARCH_PATHS = ( 448 | "$(inherited)", 449 | "@executable_path/Frameworks", 450 | ); 451 | MARKETING_VERSION = 1.0; 452 | PRODUCT_BUNDLE_IDENTIFIER = com.qutian.DemoApp; 453 | PRODUCT_NAME = "$(TARGET_NAME)"; 454 | SWIFT_EMIT_LOC_STRINGS = YES; 455 | SWIFT_VERSION = 5.0; 456 | TARGETED_DEVICE_FAMILY = "1,2"; 457 | }; 458 | name = Debug; 459 | }; 460 | C00592342C59D23100FAE9E2 /* Release */ = { 461 | isa = XCBuildConfiguration; 462 | buildSettings = { 463 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 464 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 465 | CODE_SIGN_STYLE = Automatic; 466 | CURRENT_PROJECT_VERSION = 1; 467 | DEVELOPMENT_ASSET_PATHS = "\"DemoApp/Preview Content\""; 468 | ENABLE_PREVIEWS = YES; 469 | GENERATE_INFOPLIST_FILE = YES; 470 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 471 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 472 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 473 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 474 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 475 | LD_RUNPATH_SEARCH_PATHS = ( 476 | "$(inherited)", 477 | "@executable_path/Frameworks", 478 | ); 479 | MARKETING_VERSION = 1.0; 480 | PRODUCT_BUNDLE_IDENTIFIER = com.qutian.DemoApp; 481 | PRODUCT_NAME = "$(TARGET_NAME)"; 482 | SWIFT_EMIT_LOC_STRINGS = YES; 483 | SWIFT_VERSION = 5.0; 484 | TARGETED_DEVICE_FAMILY = "1,2"; 485 | }; 486 | name = Release; 487 | }; 488 | C00592362C59D23100FAE9E2 /* Debug */ = { 489 | isa = XCBuildConfiguration; 490 | buildSettings = { 491 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 492 | BUNDLE_LOADER = "$(TEST_HOST)"; 493 | CODE_SIGN_STYLE = Automatic; 494 | CURRENT_PROJECT_VERSION = 1; 495 | GENERATE_INFOPLIST_FILE = YES; 496 | IPHONEOS_DEPLOYMENT_TARGET = 17.5; 497 | MARKETING_VERSION = 1.0; 498 | PRODUCT_BUNDLE_IDENTIFIER = com.qutian.DemoAppTests; 499 | PRODUCT_NAME = "$(TARGET_NAME)"; 500 | SWIFT_EMIT_LOC_STRINGS = NO; 501 | SWIFT_VERSION = 5.0; 502 | TARGETED_DEVICE_FAMILY = "1,2"; 503 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DemoApp.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/DemoApp"; 504 | }; 505 | name = Debug; 506 | }; 507 | C00592372C59D23100FAE9E2 /* Release */ = { 508 | isa = XCBuildConfiguration; 509 | buildSettings = { 510 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 511 | BUNDLE_LOADER = "$(TEST_HOST)"; 512 | CODE_SIGN_STYLE = Automatic; 513 | CURRENT_PROJECT_VERSION = 1; 514 | GENERATE_INFOPLIST_FILE = YES; 515 | IPHONEOS_DEPLOYMENT_TARGET = 17.5; 516 | MARKETING_VERSION = 1.0; 517 | PRODUCT_BUNDLE_IDENTIFIER = com.qutian.DemoAppTests; 518 | PRODUCT_NAME = "$(TARGET_NAME)"; 519 | SWIFT_EMIT_LOC_STRINGS = NO; 520 | SWIFT_VERSION = 5.0; 521 | TARGETED_DEVICE_FAMILY = "1,2"; 522 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DemoApp.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/DemoApp"; 523 | }; 524 | name = Release; 525 | }; 526 | C00592392C59D23100FAE9E2 /* Debug */ = { 527 | isa = XCBuildConfiguration; 528 | buildSettings = { 529 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 530 | CODE_SIGN_STYLE = Automatic; 531 | CURRENT_PROJECT_VERSION = 1; 532 | GENERATE_INFOPLIST_FILE = YES; 533 | MARKETING_VERSION = 1.0; 534 | PRODUCT_BUNDLE_IDENTIFIER = com.qutian.DemoAppUITests; 535 | PRODUCT_NAME = "$(TARGET_NAME)"; 536 | SWIFT_EMIT_LOC_STRINGS = NO; 537 | SWIFT_VERSION = 5.0; 538 | TARGETED_DEVICE_FAMILY = "1,2"; 539 | TEST_TARGET_NAME = DemoApp; 540 | }; 541 | name = Debug; 542 | }; 543 | C005923A2C59D23100FAE9E2 /* Release */ = { 544 | isa = XCBuildConfiguration; 545 | buildSettings = { 546 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 547 | CODE_SIGN_STYLE = Automatic; 548 | CURRENT_PROJECT_VERSION = 1; 549 | GENERATE_INFOPLIST_FILE = YES; 550 | MARKETING_VERSION = 1.0; 551 | PRODUCT_BUNDLE_IDENTIFIER = com.qutian.DemoAppUITests; 552 | PRODUCT_NAME = "$(TARGET_NAME)"; 553 | SWIFT_EMIT_LOC_STRINGS = NO; 554 | SWIFT_VERSION = 5.0; 555 | TARGETED_DEVICE_FAMILY = "1,2"; 556 | TEST_TARGET_NAME = DemoApp; 557 | }; 558 | name = Release; 559 | }; 560 | /* End XCBuildConfiguration section */ 561 | 562 | /* Begin XCConfigurationList section */ 563 | C00592092C59D22F00FAE9E2 /* Build configuration list for PBXProject "DemoApp" */ = { 564 | isa = XCConfigurationList; 565 | buildConfigurations = ( 566 | C00592302C59D23100FAE9E2 /* Debug */, 567 | C00592312C59D23100FAE9E2 /* Release */, 568 | ); 569 | defaultConfigurationIsVisible = 0; 570 | defaultConfigurationName = Release; 571 | }; 572 | C00592322C59D23100FAE9E2 /* Build configuration list for PBXNativeTarget "DemoApp" */ = { 573 | isa = XCConfigurationList; 574 | buildConfigurations = ( 575 | C00592332C59D23100FAE9E2 /* Debug */, 576 | C00592342C59D23100FAE9E2 /* Release */, 577 | ); 578 | defaultConfigurationIsVisible = 0; 579 | defaultConfigurationName = Release; 580 | }; 581 | C00592352C59D23100FAE9E2 /* Build configuration list for PBXNativeTarget "DemoAppTests" */ = { 582 | isa = XCConfigurationList; 583 | buildConfigurations = ( 584 | C00592362C59D23100FAE9E2 /* Debug */, 585 | C00592372C59D23100FAE9E2 /* Release */, 586 | ); 587 | defaultConfigurationIsVisible = 0; 588 | defaultConfigurationName = Release; 589 | }; 590 | C00592382C59D23100FAE9E2 /* Build configuration list for PBXNativeTarget "DemoAppUITests" */ = { 591 | isa = XCConfigurationList; 592 | buildConfigurations = ( 593 | C00592392C59D23100FAE9E2 /* Debug */, 594 | C005923A2C59D23100FAE9E2 /* Release */, 595 | ); 596 | defaultConfigurationIsVisible = 0; 597 | defaultConfigurationName = Release; 598 | }; 599 | /* End XCConfigurationList section */ 600 | 601 | /* Begin XCSwiftPackageProductDependency section */ 602 | C005923D2C59D5BC00FAE9E2 /* ContributionChart */ = { 603 | isa = XCSwiftPackageProductDependency; 604 | productName = ContributionChart; 605 | }; 606 | /* End XCSwiftPackageProductDependency section */ 607 | }; 608 | rootObject = C00592062C59D22F00FAE9E2 /* Project object */; 609 | } 610 | -------------------------------------------------------------------------------- /DemoApp/DemoApp/DemoApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DemoApp/DemoApp/DemoApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DemoApp/DemoApp/DemoApp/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 | -------------------------------------------------------------------------------- /DemoApp/DemoApp/DemoApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /DemoApp/DemoApp/DemoApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DemoApp/DemoApp/DemoApp/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // DemoApp 4 | // 5 | // Created by Qu Tian on 2024/7/31. 6 | // 7 | 8 | import SwiftUI 9 | import ContributionChart 10 | 11 | 12 | struct ContentView: View { 13 | @State private var data = [0.87, 0.17, 0.6, 0.21, 0.88, 0.41, 0.37, 0.39, 0.34, 0.91, 0.45, 0.51, 0.1, 0.97, 0.32, 0.72, 0.15, 0.2, 0.87, 0.86, 0.35, 0.12, 0.77, 0.56] 14 | 15 | var body: some View { 16 | RecordBlock { 17 | ContributionChartView(data: data, rows: 3, columns: 8, targetValue: 1.0) 18 | } 19 | .frame(width: 210, height: 95) 20 | .padding() 21 | } 22 | } 23 | 24 | fileprivate struct RecordBlock: View { 25 | @ViewBuilder var content:Content 26 | 27 | var body: some View { 28 | ZStack { 29 | RoundedRectangle(cornerRadius: 16) 30 | .fill(Color.white) 31 | .shadow(color: Color.black.opacity(0.3), radius: 16.0, y: 16.0) 32 | content 33 | } 34 | } 35 | } 36 | 37 | 38 | #Preview { 39 | ContentView() 40 | } 41 | -------------------------------------------------------------------------------- /DemoApp/DemoApp/DemoApp/DemoAppApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoAppApp.swift 3 | // DemoApp 4 | // 5 | // Created by Qu Tian on 2024/7/31. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct DemoAppApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /DemoApp/DemoApp/DemoApp/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DemoApp/DemoApp/DemoAppTests/DemoAppTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoAppTests.swift 3 | // DemoAppTests 4 | // 5 | // Created by Qu Tian on 2024/7/31. 6 | // 7 | 8 | import XCTest 9 | @testable import DemoApp 10 | 11 | final class DemoAppTests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDownWithError() throws { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | } 20 | 21 | func testExample() throws { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | // Any test you write for XCTest can be annotated as throws and async. 25 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. 26 | // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. 27 | } 28 | 29 | func testPerformanceExample() throws { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /DemoApp/DemoApp/DemoAppUITests/DemoAppUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoAppUITests.swift 3 | // DemoAppUITests 4 | // 5 | // Created by Qu Tian on 2024/7/31. 6 | // 7 | 8 | import XCTest 9 | 10 | final class DemoAppUITests: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | 15 | // In UI tests it is usually best to stop immediately when a failure occurs. 16 | continueAfterFailure = false 17 | 18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 19 | } 20 | 21 | override func tearDownWithError() throws { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | func testExample() throws { 26 | // UI tests must launch the application that they test. 27 | let app = XCUIApplication() 28 | app.launch() 29 | 30 | // Use XCTAssert and related functions to verify your tests produce the correct results. 31 | } 32 | 33 | func testLaunchPerformance() throws { 34 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { 35 | // This measures how long it takes to launch your application. 36 | measure(metrics: [XCTApplicationLaunchMetric()]) { 37 | XCUIApplication().launch() 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /DemoApp/DemoApp/DemoAppUITests/DemoAppUITestsLaunchTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoAppUITestsLaunchTests.swift 3 | // DemoAppUITests 4 | // 5 | // Created by Qu Tian on 2024/7/31. 6 | // 7 | 8 | import XCTest 9 | 10 | final class DemoAppUITestsLaunchTests: XCTestCase { 11 | 12 | override class var runsForEachTargetApplicationUIConfiguration: Bool { 13 | true 14 | } 15 | 16 | override func setUpWithError() throws { 17 | continueAfterFailure = false 18 | } 19 | 20 | func testLaunch() throws { 21 | let app = XCUIApplication() 22 | app.launch() 23 | 24 | // Insert steps here to perform after app launch but before taking a screenshot, 25 | // such as logging into a test account or navigating somewhere in the app 26 | 27 | let attachment = XCTAttachment(screenshot: app.screenshot()) 28 | attachment.name = "Launch Screen" 29 | attachment.lifetime = .keepAlways 30 | add(attachment) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.6 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "ContributionChart", 8 | platforms: [ 9 | .iOS(.v13), .macOS(.v10_15), .tvOS(.v13), .watchOS(.v6) 10 | ], 11 | products: [ 12 | // Products define the executables and libraries a package produces, and make them visible to other packages. 13 | .library( 14 | name: "ContributionChart", 15 | targets: ["ContributionChart"]), 16 | ], 17 | dependencies: [ 18 | // Dependencies declare other packages that this package depends on. 19 | // .package(url: /* package url */, from: "1.0.0"), 20 | ], 21 | targets: [ 22 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 23 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 24 | .target( 25 | name: "ContributionChart", 26 | dependencies: []) 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ContributionChart 2 | 3 | A contribution chart (aka. heatmap, GitHub-like) library for iOS, macOS, and watchOS. 4 | 5 | 100% written in SwiftUI. 6 | 7 | 8 | 9 | - [It Supports](#it-supports) 10 | * [Custom Block Number](#custom-block-number) 11 | * [Custom Block Color](#custom-block-color) 12 | * [Dark mode](#dark-mode) 13 | - [Installation](#installation) 14 | - [Usage](#usage) 15 | - [Demo Code](#demo-code) 16 | 17 | Updates 18 | - 7/31/24 Support input array with any length and add a demo app inside. 19 | 20 | # It Supports 21 | ## Custom Block Number 22 | Of course, you can also custom the **size** of blocks and **spacing** between blocks. 23 | 24 | 25 | 26 | ## Custom Block Color 27 | Here are examples using **system colors** as below, and you can custom any color you like. 28 | 29 | 30 | 31 | ## Dark mode 32 | Adjust to dark mode color automatically. 33 | 34 | 35 | 36 | # Installation 37 | Require iOS 13, macOS 10.15, watchOS 6 and Xcode 11 or higher. 38 | In Xcode go to `File -> Swift Packages -> Add Package Dependency` 39 | and paste in the repo's url: 40 | 41 | `https://github.com/VIkill33/SwiftUI-ContributionChart.git` 42 | 43 | Or you can download the code of this repo, then `Add Local...` in Xcode, and open the folder of the repo. 44 | 45 | # Usage 46 | - Import this package after you installed by `import ContributionChart` 47 | - Use the chart like 48 | ```swift 49 | ContributionChartView(data: yourData, 50 | rows: yourRows, 51 | columns: yourColumns, 52 | targetValue: yourTargetValue, 53 | blockColor: .green) 54 | ``` 55 | yourData is (**a double array**), and the targetValue is recommanded to set to the max value of the array. 56 | 57 | The color of a block will appear as exactly the color as parameter `blockColor` when its value is equal to `targetValue`, and appears light gray when is equal to zero. 58 | 59 | The top-Leading block represents the first value in array, while the bottom-trailing represents the last. And the order follows as below: 60 | 61 | 62 | 63 | # Demo Code 64 | ```swift 65 | import SwiftUI 66 | import ContributionChart 67 | 68 | struct ContentView: View { 69 | var data: [Double] 70 | let rows = 7 71 | let columns = 14 72 | 73 | init() { 74 | data = [0.3, 0.4, 0.4, 0.4, 0.1, 0.5, 0.0, 0.1, 0.0, 0.2, 0.2, 0.2, 0.0, 0.2, 0.2, 0.5, 0.4, 0.2, 0.4, 0.5, 0.2, 0.2, 0.4, 0.3, 0.3, 0.2, 0.4, 0.0, 0.0, 0.5, 0.4, 0.3, 0.5, 0.3, 0.0, 0.0, 0.1, 0.0, 0.2, 0.3, 0.0, 0.0, 0.0, 0.5, 0.3, 0.3, 0.0, 0.3, 0.0, 0.5, 0.3, 0.3, 0.4, 0.5, 0.5, 0.3, 0.4, 0.1, 0.4, 0.2, 0.5, 0.1, 0.4, 0.2, 0.5, 0.4, 0.3, 0.5, 0.0, 0.4, 0.3, 0.2, 0.1, 0.5, 0.2, 0.0, 0.2, 0.5, 0.5, 0.3, 0.4, 0.0, 0.3, 0.3, 0.1, 0.2, 0.5, 0.2, 0.1, 0.4, 0.4, 0.0, 0.5, 0.3, 0.3, 0.5, 0.0, 0.2] 75 | } 76 | 77 | var body: some View { 78 | ContributionChartView(data: data, 79 | rows: rows, 80 | columns: columns, 81 | targetValue: 0.5, 82 | blockColor: .green) 83 | } 84 | } 85 | 86 | 87 | struct ContentView_Previews: PreviewProvider { 88 | static var previews: some View { 89 | ContentView() 90 | } 91 | } 92 | 93 | ``` 94 | -------------------------------------------------------------------------------- /Sources/ContributionChart/ContributionChartTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUIView.swift 3 | // 4 | // 5 | // Created by Vikill Blacks on 2023/2/2. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SwiftUIView: View { 11 | var body: some View { 12 | ContributionChartView(data: [0,3,4,2,4,2,5,1,3], rows: 3, columns: 3, targetValue: 5, RectangleRadius: 2.0) 13 | } 14 | } 15 | 16 | struct SwiftUIView_Previews: PreviewProvider { 17 | static var previews: some View { 18 | SwiftUIView() 19 | //.preferredColorScheme(.dark) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/ContributionChart/ContributionChartView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @available(iOS 13, macOS 10.15, watchOS 6, *) 4 | public struct ContributionChartView: View { 5 | 6 | var data: [Double] 7 | var rows: Int 8 | var columns: Int 9 | var targetValue: Double 10 | var blockColor: Color = Color.green 11 | var blockBackgroundColor: Color = Color.background 12 | 13 | var heatMapRectangleWidth: Double = 20.0 14 | var heatMapRectangleSpacing: Double = 2.0 15 | var heatMapRectangleRadius: Double = 5.0 16 | 17 | public init(data: [Double], rows: Int, columns: Int, targetValue: Double, blockColor: Color = Color.green, blockBackgroundColor: Color, RectangleWidth: Double = 20.0, RectangleSpacing: Double = 2.0, RectangleRadius: Double = 5.0){ 18 | self.data = data 19 | self.rows = rows 20 | self.columns = columns 21 | self.targetValue = targetValue 22 | self.blockColor = blockColor 23 | self.blockBackgroundColor = blockBackgroundColor 24 | self.heatMapRectangleWidth = RectangleWidth 25 | self.heatMapRectangleSpacing = RectangleSpacing 26 | self.heatMapRectangleRadius = RectangleRadius 27 | } 28 | 29 | public init(data: [Double], rows: Int, columns: Int, targetValue: Double, blockColor: Color = Color.green, RectangleWidth: Double = 20.0, RectangleSpacing: Double = 2.0, RectangleRadius: Double = 5.0){ 30 | self.data = data 31 | self.rows = rows 32 | self.columns = columns 33 | if data.count < rows * columns { 34 | let elementsToAdd = Array(repeating: 0.0, count: rows * columns - data.count) 35 | self.data.append(contentsOf: elementsToAdd) 36 | } 37 | self.targetValue = targetValue 38 | self.blockColor = blockColor 39 | self.heatMapRectangleWidth = RectangleWidth 40 | self.heatMapRectangleSpacing = RectangleSpacing 41 | self.heatMapRectangleRadius = RectangleRadius 42 | } 43 | 44 | public var body: some View { 45 | VStack { 46 | // Chart 47 | GeometryReader { geo in 48 | ZStack { 49 | HStack(spacing: heatMapRectangleSpacing) { 50 | ForEach(0.. Double { 110 | let opacityRatio: Double = Double(rowData[index]) / Double(targetValue) 111 | return opacityRatio > 1.0 ? 1.0 : opacityRatio 112 | } 113 | 114 | } 115 | 116 | extension Color { 117 | init(hexString: String) { 118 | let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) 119 | var int = UInt64() 120 | Scanner(string: hex).scanHexInt64(&int) 121 | let r, g, b: UInt64 122 | switch hex.count { 123 | case 3: // RGB (12-bit) 124 | (r, g, b) = ((int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) 125 | case 6: // RGB (24-bit) 126 | (r, g, b) = (int >> 16, int >> 8 & 0xFF, int & 0xFF) 127 | case 8: // ARGB (32-bit) 128 | (r, g, b) = (int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) 129 | default: 130 | (r, g, b) = (0, 0, 0) 131 | } 132 | self.init(red: Double(r) / 255, green: Double(g) / 255, blue: Double(b) / 255) 133 | } 134 | #if os(macOS) 135 | static let background = Color(NSColor.windowBackgroundColor) 136 | static let secondaryBackground = Color(NSColor.underPageBackgroundColor) 137 | static let tertiaryBackground = Color(NSColor.controlBackgroundColor) 138 | #endif 139 | #if os(iOS) 140 | static let background = Color(UIColor.systemBackground) 141 | static let secondaryBackground = Color(UIColor.secondarySystemBackground) 142 | static let tertiaryBackground = Color(UIColor.tertiarySystemBackground) 143 | #endif 144 | #if os(watchOS) 145 | static let background = Color.black 146 | #endif 147 | } 148 | --------------------------------------------------------------------------------