├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── ChartJS Demo ├── ChartJS Demo.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── ChartJS Demo │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── ChartJS_Demo.entitlements │ ├── ChartJS_DemoApp.swift │ ├── Charts │ │ ├── Bar.swift │ │ ├── Line.swift │ │ ├── Pie.swift │ │ └── Polar.swift │ └── ContentView.swift ├── ChartJS DemoTests │ └── ChartJS_DemoTests.swift └── ChartJS DemoUITests │ ├── ChartJS_DemoUITests.swift │ └── ChartJS_DemoUITestsLaunchTests.swift ├── LICENSE ├── Package.swift ├── README.md ├── README ├── README.es.md ├── README.ja.md ├── README.zh-CN.md ├── README.zh-HK.md └── README.zh-TW.md ├── Sources └── ChartJS │ ├── ChartJS.swift │ └── Resources │ └── chart.js └── Tests └── ChartJSTests └── ChartJSTests.swift /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: 1998code 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /ChartJS Demo/ChartJS Demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 77; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3A988AE92DDE12BD00632266 /* ChartJS in Frameworks */ = {isa = PBXBuildFile; productRef = 3A988AE82DDE12BD00632266 /* ChartJS */; }; 11 | 3A988AEC2DDE12FA00632266 /* ChartJS in Frameworks */ = {isa = PBXBuildFile; productRef = 3A988AEB2DDE12FA00632266 /* ChartJS */; }; 12 | 3A988B0A2DDE193200632266 /* ChartJS in Frameworks */ = {isa = PBXBuildFile; productRef = 3A988B092DDE193200632266 /* ChartJS */; }; 13 | /* End PBXBuildFile section */ 14 | 15 | /* Begin PBXContainerItemProxy section */ 16 | 3A988ACB2DDE12AB00632266 /* PBXContainerItemProxy */ = { 17 | isa = PBXContainerItemProxy; 18 | containerPortal = 3A988AB42DDE12A900632266 /* Project object */; 19 | proxyType = 1; 20 | remoteGlobalIDString = 3A988ABB2DDE12A900632266; 21 | remoteInfo = "ChartJS Demo"; 22 | }; 23 | 3A988AD52DDE12AB00632266 /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = 3A988AB42DDE12A900632266 /* Project object */; 26 | proxyType = 1; 27 | remoteGlobalIDString = 3A988ABB2DDE12A900632266; 28 | remoteInfo = "ChartJS Demo"; 29 | }; 30 | /* End PBXContainerItemProxy section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 3A988ABC2DDE12A900632266 /* ChartJS Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ChartJS Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | 3A988ACA2DDE12AB00632266 /* ChartJS DemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "ChartJS DemoTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 3A988AD42DDE12AB00632266 /* ChartJS DemoUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "ChartJS DemoUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | /* End PBXFileReference section */ 37 | 38 | /* Begin PBXFileSystemSynchronizedRootGroup section */ 39 | 3A988ABE2DDE12A900632266 /* ChartJS Demo */ = { 40 | isa = PBXFileSystemSynchronizedRootGroup; 41 | path = "ChartJS Demo"; 42 | sourceTree = ""; 43 | }; 44 | 3A988ACD2DDE12AB00632266 /* ChartJS DemoTests */ = { 45 | isa = PBXFileSystemSynchronizedRootGroup; 46 | path = "ChartJS DemoTests"; 47 | sourceTree = ""; 48 | }; 49 | 3A988AD72DDE12AB00632266 /* ChartJS DemoUITests */ = { 50 | isa = PBXFileSystemSynchronizedRootGroup; 51 | path = "ChartJS DemoUITests"; 52 | sourceTree = ""; 53 | }; 54 | /* End PBXFileSystemSynchronizedRootGroup section */ 55 | 56 | /* Begin PBXFrameworksBuildPhase section */ 57 | 3A988AB92DDE12A900632266 /* Frameworks */ = { 58 | isa = PBXFrameworksBuildPhase; 59 | buildActionMask = 2147483647; 60 | files = ( 61 | 3A988B0A2DDE193200632266 /* ChartJS in Frameworks */, 62 | 3A988AEC2DDE12FA00632266 /* ChartJS in Frameworks */, 63 | 3A988AE92DDE12BD00632266 /* ChartJS in Frameworks */, 64 | ); 65 | runOnlyForDeploymentPostprocessing = 0; 66 | }; 67 | 3A988AC72DDE12AB00632266 /* Frameworks */ = { 68 | isa = PBXFrameworksBuildPhase; 69 | buildActionMask = 2147483647; 70 | files = ( 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | 3A988AD12DDE12AB00632266 /* Frameworks */ = { 75 | isa = PBXFrameworksBuildPhase; 76 | buildActionMask = 2147483647; 77 | files = ( 78 | ); 79 | runOnlyForDeploymentPostprocessing = 0; 80 | }; 81 | /* End PBXFrameworksBuildPhase section */ 82 | 83 | /* Begin PBXGroup section */ 84 | 3A988AB32DDE12A900632266 = { 85 | isa = PBXGroup; 86 | children = ( 87 | 3A988ABE2DDE12A900632266 /* ChartJS Demo */, 88 | 3A988ACD2DDE12AB00632266 /* ChartJS DemoTests */, 89 | 3A988AD72DDE12AB00632266 /* ChartJS DemoUITests */, 90 | 3A988ABD2DDE12A900632266 /* Products */, 91 | ); 92 | sourceTree = ""; 93 | }; 94 | 3A988ABD2DDE12A900632266 /* Products */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 3A988ABC2DDE12A900632266 /* ChartJS Demo.app */, 98 | 3A988ACA2DDE12AB00632266 /* ChartJS DemoTests.xctest */, 99 | 3A988AD42DDE12AB00632266 /* ChartJS DemoUITests.xctest */, 100 | ); 101 | name = Products; 102 | sourceTree = ""; 103 | }; 104 | /* End PBXGroup section */ 105 | 106 | /* Begin PBXNativeTarget section */ 107 | 3A988ABB2DDE12A900632266 /* ChartJS Demo */ = { 108 | isa = PBXNativeTarget; 109 | buildConfigurationList = 3A988ADE2DDE12AB00632266 /* Build configuration list for PBXNativeTarget "ChartJS Demo" */; 110 | buildPhases = ( 111 | 3A988AB82DDE12A900632266 /* Sources */, 112 | 3A988AB92DDE12A900632266 /* Frameworks */, 113 | 3A988ABA2DDE12A900632266 /* Resources */, 114 | ); 115 | buildRules = ( 116 | ); 117 | dependencies = ( 118 | ); 119 | fileSystemSynchronizedGroups = ( 120 | 3A988ABE2DDE12A900632266 /* ChartJS Demo */, 121 | ); 122 | name = "ChartJS Demo"; 123 | packageProductDependencies = ( 124 | 3A988AE82DDE12BD00632266 /* ChartJS */, 125 | 3A988AEB2DDE12FA00632266 /* ChartJS */, 126 | 3A988B092DDE193200632266 /* ChartJS */, 127 | ); 128 | productName = "ChartJS Demo"; 129 | productReference = 3A988ABC2DDE12A900632266 /* ChartJS Demo.app */; 130 | productType = "com.apple.product-type.application"; 131 | }; 132 | 3A988AC92DDE12AB00632266 /* ChartJS DemoTests */ = { 133 | isa = PBXNativeTarget; 134 | buildConfigurationList = 3A988AE12DDE12AB00632266 /* Build configuration list for PBXNativeTarget "ChartJS DemoTests" */; 135 | buildPhases = ( 136 | 3A988AC62DDE12AB00632266 /* Sources */, 137 | 3A988AC72DDE12AB00632266 /* Frameworks */, 138 | 3A988AC82DDE12AB00632266 /* Resources */, 139 | ); 140 | buildRules = ( 141 | ); 142 | dependencies = ( 143 | 3A988ACC2DDE12AB00632266 /* PBXTargetDependency */, 144 | ); 145 | fileSystemSynchronizedGroups = ( 146 | 3A988ACD2DDE12AB00632266 /* ChartJS DemoTests */, 147 | ); 148 | name = "ChartJS DemoTests"; 149 | packageProductDependencies = ( 150 | ); 151 | productName = "ChartJS DemoTests"; 152 | productReference = 3A988ACA2DDE12AB00632266 /* ChartJS DemoTests.xctest */; 153 | productType = "com.apple.product-type.bundle.unit-test"; 154 | }; 155 | 3A988AD32DDE12AB00632266 /* ChartJS DemoUITests */ = { 156 | isa = PBXNativeTarget; 157 | buildConfigurationList = 3A988AE42DDE12AB00632266 /* Build configuration list for PBXNativeTarget "ChartJS DemoUITests" */; 158 | buildPhases = ( 159 | 3A988AD02DDE12AB00632266 /* Sources */, 160 | 3A988AD12DDE12AB00632266 /* Frameworks */, 161 | 3A988AD22DDE12AB00632266 /* Resources */, 162 | ); 163 | buildRules = ( 164 | ); 165 | dependencies = ( 166 | 3A988AD62DDE12AB00632266 /* PBXTargetDependency */, 167 | ); 168 | fileSystemSynchronizedGroups = ( 169 | 3A988AD72DDE12AB00632266 /* ChartJS DemoUITests */, 170 | ); 171 | name = "ChartJS DemoUITests"; 172 | packageProductDependencies = ( 173 | ); 174 | productName = "ChartJS DemoUITests"; 175 | productReference = 3A988AD42DDE12AB00632266 /* ChartJS DemoUITests.xctest */; 176 | productType = "com.apple.product-type.bundle.ui-testing"; 177 | }; 178 | /* End PBXNativeTarget section */ 179 | 180 | /* Begin PBXProject section */ 181 | 3A988AB42DDE12A900632266 /* Project object */ = { 182 | isa = PBXProject; 183 | attributes = { 184 | BuildIndependentTargetsInParallel = 1; 185 | LastSwiftUpdateCheck = 1630; 186 | LastUpgradeCheck = 1630; 187 | TargetAttributes = { 188 | 3A988ABB2DDE12A900632266 = { 189 | CreatedOnToolsVersion = 16.3; 190 | }; 191 | 3A988AC92DDE12AB00632266 = { 192 | CreatedOnToolsVersion = 16.3; 193 | TestTargetID = 3A988ABB2DDE12A900632266; 194 | }; 195 | 3A988AD32DDE12AB00632266 = { 196 | CreatedOnToolsVersion = 16.3; 197 | TestTargetID = 3A988ABB2DDE12A900632266; 198 | }; 199 | }; 200 | }; 201 | buildConfigurationList = 3A988AB72DDE12A900632266 /* Build configuration list for PBXProject "ChartJS Demo" */; 202 | developmentRegion = en; 203 | hasScannedForEncodings = 0; 204 | knownRegions = ( 205 | en, 206 | Base, 207 | ); 208 | mainGroup = 3A988AB32DDE12A900632266; 209 | minimizedProjectReferenceProxies = 1; 210 | packageReferences = ( 211 | 3A988B082DDE193200632266 /* XCLocalSwiftPackageReference "../../ChartJS-for-Swift" */, 212 | ); 213 | preferredProjectObjectVersion = 77; 214 | productRefGroup = 3A988ABD2DDE12A900632266 /* Products */; 215 | projectDirPath = ""; 216 | projectRoot = ""; 217 | targets = ( 218 | 3A988ABB2DDE12A900632266 /* ChartJS Demo */, 219 | 3A988AC92DDE12AB00632266 /* ChartJS DemoTests */, 220 | 3A988AD32DDE12AB00632266 /* ChartJS DemoUITests */, 221 | ); 222 | }; 223 | /* End PBXProject section */ 224 | 225 | /* Begin PBXResourcesBuildPhase section */ 226 | 3A988ABA2DDE12A900632266 /* Resources */ = { 227 | isa = PBXResourcesBuildPhase; 228 | buildActionMask = 2147483647; 229 | files = ( 230 | ); 231 | runOnlyForDeploymentPostprocessing = 0; 232 | }; 233 | 3A988AC82DDE12AB00632266 /* Resources */ = { 234 | isa = PBXResourcesBuildPhase; 235 | buildActionMask = 2147483647; 236 | files = ( 237 | ); 238 | runOnlyForDeploymentPostprocessing = 0; 239 | }; 240 | 3A988AD22DDE12AB00632266 /* Resources */ = { 241 | isa = PBXResourcesBuildPhase; 242 | buildActionMask = 2147483647; 243 | files = ( 244 | ); 245 | runOnlyForDeploymentPostprocessing = 0; 246 | }; 247 | /* End PBXResourcesBuildPhase section */ 248 | 249 | /* Begin PBXSourcesBuildPhase section */ 250 | 3A988AB82DDE12A900632266 /* Sources */ = { 251 | isa = PBXSourcesBuildPhase; 252 | buildActionMask = 2147483647; 253 | files = ( 254 | ); 255 | runOnlyForDeploymentPostprocessing = 0; 256 | }; 257 | 3A988AC62DDE12AB00632266 /* Sources */ = { 258 | isa = PBXSourcesBuildPhase; 259 | buildActionMask = 2147483647; 260 | files = ( 261 | ); 262 | runOnlyForDeploymentPostprocessing = 0; 263 | }; 264 | 3A988AD02DDE12AB00632266 /* Sources */ = { 265 | isa = PBXSourcesBuildPhase; 266 | buildActionMask = 2147483647; 267 | files = ( 268 | ); 269 | runOnlyForDeploymentPostprocessing = 0; 270 | }; 271 | /* End PBXSourcesBuildPhase section */ 272 | 273 | /* Begin PBXTargetDependency section */ 274 | 3A988ACC2DDE12AB00632266 /* PBXTargetDependency */ = { 275 | isa = PBXTargetDependency; 276 | target = 3A988ABB2DDE12A900632266 /* ChartJS Demo */; 277 | targetProxy = 3A988ACB2DDE12AB00632266 /* PBXContainerItemProxy */; 278 | }; 279 | 3A988AD62DDE12AB00632266 /* PBXTargetDependency */ = { 280 | isa = PBXTargetDependency; 281 | target = 3A988ABB2DDE12A900632266 /* ChartJS Demo */; 282 | targetProxy = 3A988AD52DDE12AB00632266 /* PBXContainerItemProxy */; 283 | }; 284 | /* End PBXTargetDependency section */ 285 | 286 | /* Begin XCBuildConfiguration section */ 287 | 3A988ADC2DDE12AB00632266 /* Debug */ = { 288 | isa = XCBuildConfiguration; 289 | buildSettings = { 290 | ALWAYS_SEARCH_USER_PATHS = NO; 291 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 292 | CLANG_ANALYZER_NONNULL = YES; 293 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 294 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 295 | CLANG_ENABLE_MODULES = YES; 296 | CLANG_ENABLE_OBJC_ARC = YES; 297 | CLANG_ENABLE_OBJC_WEAK = YES; 298 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 299 | CLANG_WARN_BOOL_CONVERSION = YES; 300 | CLANG_WARN_COMMA = YES; 301 | CLANG_WARN_CONSTANT_CONVERSION = YES; 302 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 303 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 304 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 305 | CLANG_WARN_EMPTY_BODY = YES; 306 | CLANG_WARN_ENUM_CONVERSION = YES; 307 | CLANG_WARN_INFINITE_RECURSION = YES; 308 | CLANG_WARN_INT_CONVERSION = YES; 309 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 310 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 311 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 312 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 313 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 314 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 315 | CLANG_WARN_STRICT_PROTOTYPES = YES; 316 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 317 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 318 | CLANG_WARN_UNREACHABLE_CODE = YES; 319 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 320 | COPY_PHASE_STRIP = NO; 321 | DEBUG_INFORMATION_FORMAT = dwarf; 322 | ENABLE_STRICT_OBJC_MSGSEND = YES; 323 | ENABLE_TESTABILITY = YES; 324 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 325 | GCC_C_LANGUAGE_STANDARD = gnu17; 326 | GCC_DYNAMIC_NO_PIC = NO; 327 | GCC_NO_COMMON_BLOCKS = YES; 328 | GCC_OPTIMIZATION_LEVEL = 0; 329 | GCC_PREPROCESSOR_DEFINITIONS = ( 330 | "DEBUG=1", 331 | "$(inherited)", 332 | ); 333 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 334 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 335 | GCC_WARN_UNDECLARED_SELECTOR = YES; 336 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 337 | GCC_WARN_UNUSED_FUNCTION = YES; 338 | GCC_WARN_UNUSED_VARIABLE = YES; 339 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 340 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 341 | MTL_FAST_MATH = YES; 342 | ONLY_ACTIVE_ARCH = YES; 343 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 344 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 345 | }; 346 | name = Debug; 347 | }; 348 | 3A988ADD2DDE12AB00632266 /* Release */ = { 349 | isa = XCBuildConfiguration; 350 | buildSettings = { 351 | ALWAYS_SEARCH_USER_PATHS = NO; 352 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 353 | CLANG_ANALYZER_NONNULL = YES; 354 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 355 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 356 | CLANG_ENABLE_MODULES = YES; 357 | CLANG_ENABLE_OBJC_ARC = YES; 358 | CLANG_ENABLE_OBJC_WEAK = YES; 359 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 360 | CLANG_WARN_BOOL_CONVERSION = YES; 361 | CLANG_WARN_COMMA = YES; 362 | CLANG_WARN_CONSTANT_CONVERSION = YES; 363 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 364 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 365 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 366 | CLANG_WARN_EMPTY_BODY = YES; 367 | CLANG_WARN_ENUM_CONVERSION = YES; 368 | CLANG_WARN_INFINITE_RECURSION = YES; 369 | CLANG_WARN_INT_CONVERSION = YES; 370 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 371 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 372 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 373 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 374 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 375 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 376 | CLANG_WARN_STRICT_PROTOTYPES = YES; 377 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 378 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 379 | CLANG_WARN_UNREACHABLE_CODE = YES; 380 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 381 | COPY_PHASE_STRIP = NO; 382 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 383 | ENABLE_NS_ASSERTIONS = NO; 384 | ENABLE_STRICT_OBJC_MSGSEND = YES; 385 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 386 | GCC_C_LANGUAGE_STANDARD = gnu17; 387 | GCC_NO_COMMON_BLOCKS = YES; 388 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 389 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 390 | GCC_WARN_UNDECLARED_SELECTOR = YES; 391 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 392 | GCC_WARN_UNUSED_FUNCTION = YES; 393 | GCC_WARN_UNUSED_VARIABLE = YES; 394 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 395 | MTL_ENABLE_DEBUG_INFO = NO; 396 | MTL_FAST_MATH = YES; 397 | SWIFT_COMPILATION_MODE = wholemodule; 398 | }; 399 | name = Release; 400 | }; 401 | 3A988ADF2DDE12AB00632266 /* Debug */ = { 402 | isa = XCBuildConfiguration; 403 | buildSettings = { 404 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 405 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 406 | CODE_SIGN_ENTITLEMENTS = "ChartJS Demo/ChartJS_Demo.entitlements"; 407 | CODE_SIGN_STYLE = Automatic; 408 | CURRENT_PROJECT_VERSION = 1; 409 | ENABLE_PREVIEWS = YES; 410 | GENERATE_INFOPLIST_FILE = YES; 411 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 412 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 413 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 414 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 415 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 416 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 417 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; 418 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; 419 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 420 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 421 | IPHONEOS_DEPLOYMENT_TARGET = 18.4; 422 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 423 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 424 | MACOSX_DEPLOYMENT_TARGET = 15.4; 425 | MARKETING_VERSION = 1.0; 426 | PRODUCT_BUNDLE_IDENTIFIER = "media.1998.ChartJS-Demo"; 427 | PRODUCT_NAME = "$(TARGET_NAME)"; 428 | REGISTER_APP_GROUPS = YES; 429 | SDKROOT = auto; 430 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; 431 | SWIFT_EMIT_LOC_STRINGS = YES; 432 | SWIFT_VERSION = 5.0; 433 | TARGETED_DEVICE_FAMILY = "1,2,7"; 434 | XROS_DEPLOYMENT_TARGET = 2.4; 435 | }; 436 | name = Debug; 437 | }; 438 | 3A988AE02DDE12AB00632266 /* Release */ = { 439 | isa = XCBuildConfiguration; 440 | buildSettings = { 441 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 442 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 443 | CODE_SIGN_ENTITLEMENTS = "ChartJS Demo/ChartJS_Demo.entitlements"; 444 | CODE_SIGN_STYLE = Automatic; 445 | CURRENT_PROJECT_VERSION = 1; 446 | ENABLE_PREVIEWS = YES; 447 | GENERATE_INFOPLIST_FILE = YES; 448 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 449 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 450 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 451 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 452 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 453 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 454 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; 455 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; 456 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 457 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 458 | IPHONEOS_DEPLOYMENT_TARGET = 18.4; 459 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 460 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 461 | MACOSX_DEPLOYMENT_TARGET = 15.4; 462 | MARKETING_VERSION = 1.0; 463 | PRODUCT_BUNDLE_IDENTIFIER = "media.1998.ChartJS-Demo"; 464 | PRODUCT_NAME = "$(TARGET_NAME)"; 465 | REGISTER_APP_GROUPS = YES; 466 | SDKROOT = auto; 467 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; 468 | SWIFT_EMIT_LOC_STRINGS = YES; 469 | SWIFT_VERSION = 5.0; 470 | TARGETED_DEVICE_FAMILY = "1,2,7"; 471 | XROS_DEPLOYMENT_TARGET = 2.4; 472 | }; 473 | name = Release; 474 | }; 475 | 3A988AE22DDE12AB00632266 /* Debug */ = { 476 | isa = XCBuildConfiguration; 477 | buildSettings = { 478 | BUNDLE_LOADER = "$(TEST_HOST)"; 479 | CODE_SIGN_STYLE = Automatic; 480 | CURRENT_PROJECT_VERSION = 1; 481 | GENERATE_INFOPLIST_FILE = YES; 482 | IPHONEOS_DEPLOYMENT_TARGET = 18.4; 483 | MACOSX_DEPLOYMENT_TARGET = 15.4; 484 | MARKETING_VERSION = 1.0; 485 | PRODUCT_BUNDLE_IDENTIFIER = "media.1998.ChartJS-DemoTests"; 486 | PRODUCT_NAME = "$(TARGET_NAME)"; 487 | SDKROOT = auto; 488 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; 489 | SWIFT_EMIT_LOC_STRINGS = NO; 490 | SWIFT_VERSION = 5.0; 491 | TARGETED_DEVICE_FAMILY = "1,2,7"; 492 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ChartJS Demo.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/ChartJS Demo"; 493 | XROS_DEPLOYMENT_TARGET = 2.4; 494 | }; 495 | name = Debug; 496 | }; 497 | 3A988AE32DDE12AB00632266 /* Release */ = { 498 | isa = XCBuildConfiguration; 499 | buildSettings = { 500 | BUNDLE_LOADER = "$(TEST_HOST)"; 501 | CODE_SIGN_STYLE = Automatic; 502 | CURRENT_PROJECT_VERSION = 1; 503 | GENERATE_INFOPLIST_FILE = YES; 504 | IPHONEOS_DEPLOYMENT_TARGET = 18.4; 505 | MACOSX_DEPLOYMENT_TARGET = 15.4; 506 | MARKETING_VERSION = 1.0; 507 | PRODUCT_BUNDLE_IDENTIFIER = "media.1998.ChartJS-DemoTests"; 508 | PRODUCT_NAME = "$(TARGET_NAME)"; 509 | SDKROOT = auto; 510 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; 511 | SWIFT_EMIT_LOC_STRINGS = NO; 512 | SWIFT_VERSION = 5.0; 513 | TARGETED_DEVICE_FAMILY = "1,2,7"; 514 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ChartJS Demo.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/ChartJS Demo"; 515 | XROS_DEPLOYMENT_TARGET = 2.4; 516 | }; 517 | name = Release; 518 | }; 519 | 3A988AE52DDE12AB00632266 /* Debug */ = { 520 | isa = XCBuildConfiguration; 521 | buildSettings = { 522 | CODE_SIGN_STYLE = Automatic; 523 | CURRENT_PROJECT_VERSION = 1; 524 | GENERATE_INFOPLIST_FILE = YES; 525 | IPHONEOS_DEPLOYMENT_TARGET = 18.4; 526 | MACOSX_DEPLOYMENT_TARGET = 15.4; 527 | MARKETING_VERSION = 1.0; 528 | PRODUCT_BUNDLE_IDENTIFIER = "media.1998.ChartJS-DemoUITests"; 529 | PRODUCT_NAME = "$(TARGET_NAME)"; 530 | SDKROOT = auto; 531 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; 532 | SWIFT_EMIT_LOC_STRINGS = NO; 533 | SWIFT_VERSION = 5.0; 534 | TARGETED_DEVICE_FAMILY = "1,2,7"; 535 | TEST_TARGET_NAME = "ChartJS Demo"; 536 | XROS_DEPLOYMENT_TARGET = 2.4; 537 | }; 538 | name = Debug; 539 | }; 540 | 3A988AE62DDE12AB00632266 /* Release */ = { 541 | isa = XCBuildConfiguration; 542 | buildSettings = { 543 | CODE_SIGN_STYLE = Automatic; 544 | CURRENT_PROJECT_VERSION = 1; 545 | GENERATE_INFOPLIST_FILE = YES; 546 | IPHONEOS_DEPLOYMENT_TARGET = 18.4; 547 | MACOSX_DEPLOYMENT_TARGET = 15.4; 548 | MARKETING_VERSION = 1.0; 549 | PRODUCT_BUNDLE_IDENTIFIER = "media.1998.ChartJS-DemoUITests"; 550 | PRODUCT_NAME = "$(TARGET_NAME)"; 551 | SDKROOT = auto; 552 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; 553 | SWIFT_EMIT_LOC_STRINGS = NO; 554 | SWIFT_VERSION = 5.0; 555 | TARGETED_DEVICE_FAMILY = "1,2,7"; 556 | TEST_TARGET_NAME = "ChartJS Demo"; 557 | XROS_DEPLOYMENT_TARGET = 2.4; 558 | }; 559 | name = Release; 560 | }; 561 | /* End XCBuildConfiguration section */ 562 | 563 | /* Begin XCConfigurationList section */ 564 | 3A988AB72DDE12A900632266 /* Build configuration list for PBXProject "ChartJS Demo" */ = { 565 | isa = XCConfigurationList; 566 | buildConfigurations = ( 567 | 3A988ADC2DDE12AB00632266 /* Debug */, 568 | 3A988ADD2DDE12AB00632266 /* Release */, 569 | ); 570 | defaultConfigurationIsVisible = 0; 571 | defaultConfigurationName = Release; 572 | }; 573 | 3A988ADE2DDE12AB00632266 /* Build configuration list for PBXNativeTarget "ChartJS Demo" */ = { 574 | isa = XCConfigurationList; 575 | buildConfigurations = ( 576 | 3A988ADF2DDE12AB00632266 /* Debug */, 577 | 3A988AE02DDE12AB00632266 /* Release */, 578 | ); 579 | defaultConfigurationIsVisible = 0; 580 | defaultConfigurationName = Release; 581 | }; 582 | 3A988AE12DDE12AB00632266 /* Build configuration list for PBXNativeTarget "ChartJS DemoTests" */ = { 583 | isa = XCConfigurationList; 584 | buildConfigurations = ( 585 | 3A988AE22DDE12AB00632266 /* Debug */, 586 | 3A988AE32DDE12AB00632266 /* Release */, 587 | ); 588 | defaultConfigurationIsVisible = 0; 589 | defaultConfigurationName = Release; 590 | }; 591 | 3A988AE42DDE12AB00632266 /* Build configuration list for PBXNativeTarget "ChartJS DemoUITests" */ = { 592 | isa = XCConfigurationList; 593 | buildConfigurations = ( 594 | 3A988AE52DDE12AB00632266 /* Debug */, 595 | 3A988AE62DDE12AB00632266 /* Release */, 596 | ); 597 | defaultConfigurationIsVisible = 0; 598 | defaultConfigurationName = Release; 599 | }; 600 | /* End XCConfigurationList section */ 601 | 602 | /* Begin XCLocalSwiftPackageReference section */ 603 | 3A988B082DDE193200632266 /* XCLocalSwiftPackageReference "../../ChartJS-for-Swift" */ = { 604 | isa = XCLocalSwiftPackageReference; 605 | relativePath = "../../ChartJS-for-Swift"; 606 | }; 607 | /* End XCLocalSwiftPackageReference section */ 608 | 609 | /* Begin XCSwiftPackageProductDependency section */ 610 | 3A988AE82DDE12BD00632266 /* ChartJS */ = { 611 | isa = XCSwiftPackageProductDependency; 612 | productName = ChartJS; 613 | }; 614 | 3A988AEB2DDE12FA00632266 /* ChartJS */ = { 615 | isa = XCSwiftPackageProductDependency; 616 | productName = ChartJS; 617 | }; 618 | 3A988B092DDE193200632266 /* ChartJS */ = { 619 | isa = XCSwiftPackageProductDependency; 620 | productName = ChartJS; 621 | }; 622 | /* End XCSwiftPackageProductDependency section */ 623 | }; 624 | rootObject = 3A988AB42DDE12A900632266 /* Project object */; 625 | } 626 | -------------------------------------------------------------------------------- /ChartJS Demo/ChartJS Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ChartJS Demo/ChartJS Demo/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 | -------------------------------------------------------------------------------- /ChartJS Demo/ChartJS Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | }, 8 | { 9 | "appearances" : [ 10 | { 11 | "appearance" : "luminosity", 12 | "value" : "dark" 13 | } 14 | ], 15 | "idiom" : "universal", 16 | "platform" : "ios", 17 | "size" : "1024x1024" 18 | }, 19 | { 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "tinted" 24 | } 25 | ], 26 | "idiom" : "universal", 27 | "platform" : "ios", 28 | "size" : "1024x1024" 29 | }, 30 | { 31 | "idiom" : "mac", 32 | "scale" : "1x", 33 | "size" : "16x16" 34 | }, 35 | { 36 | "idiom" : "mac", 37 | "scale" : "2x", 38 | "size" : "16x16" 39 | }, 40 | { 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "32x32" 44 | }, 45 | { 46 | "idiom" : "mac", 47 | "scale" : "2x", 48 | "size" : "32x32" 49 | }, 50 | { 51 | "idiom" : "mac", 52 | "scale" : "1x", 53 | "size" : "128x128" 54 | }, 55 | { 56 | "idiom" : "mac", 57 | "scale" : "2x", 58 | "size" : "128x128" 59 | }, 60 | { 61 | "idiom" : "mac", 62 | "scale" : "1x", 63 | "size" : "256x256" 64 | }, 65 | { 66 | "idiom" : "mac", 67 | "scale" : "2x", 68 | "size" : "256x256" 69 | }, 70 | { 71 | "idiom" : "mac", 72 | "scale" : "1x", 73 | "size" : "512x512" 74 | }, 75 | { 76 | "idiom" : "mac", 77 | "scale" : "2x", 78 | "size" : "512x512" 79 | } 80 | ], 81 | "info" : { 82 | "author" : "xcode", 83 | "version" : 1 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /ChartJS Demo/ChartJS Demo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ChartJS Demo/ChartJS Demo/ChartJS_Demo.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 | -------------------------------------------------------------------------------- /ChartJS Demo/ChartJS Demo/ChartJS_DemoApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChartJS_DemoApp.swift 3 | // ChartJS Demo 4 | // 5 | // Created by Ming on 21/5/2025. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct ChartJS_DemoApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | Text("Demo") 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ChartJS Demo/ChartJS Demo/Charts/Bar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bar.swift 3 | // ChartJS 4 | // 5 | // Created by Ming on 21/5/2025. 6 | // 7 | 8 | import SwiftUI 9 | import ChartJS 10 | 11 | #Preview("Border Radius Chart") { 12 | let dataJson = """ 13 | { 14 | "labels": ["January", "February", "March", "April", "May", "June", "July"], 15 | "datasets": [ 16 | { 17 | "label": "Fully Rounded", 18 | "data": [65, -59, 80, -81, 56, -55, 40], 19 | "borderColor": "rgb(255, 99, 132)", 20 | "backgroundColor": "rgba(255, 99, 132, 0.5)", 21 | "borderWidth": 2, 22 | "borderRadius": 999, 23 | "borderSkipped": false 24 | }, 25 | { 26 | "label": "Small Radius", 27 | "data": [28, -48, 40, -19, 86, -27, 90], 28 | "borderColor": "rgb(54, 162, 235)", 29 | "backgroundColor": "rgba(54, 162, 235, 0.5)", 30 | "borderWidth": 2, 31 | "borderRadius": 5, 32 | "borderSkipped": false 33 | } 34 | ] 35 | } 36 | """ 37 | 38 | let configJson = """ 39 | { 40 | "type": "bar", 41 | "options": { 42 | "responsive": true, 43 | "plugins": { 44 | "legend": { 45 | "position": "top" 46 | }, 47 | "title": { 48 | "display": true, 49 | "text": "Bar Chart Border Radius" 50 | } 51 | } 52 | } 53 | } 54 | """ 55 | 56 | return GeometryReader { geometry in 57 | Charts(dataJson: dataJson, configJson: configJson) 58 | .frame(height: geometry.size.width > geometry.size.height ? 59 | geometry.size.height : // landscape 60 | geometry.size.height * 0.3) // portrait 61 | .padding() 62 | .frame(maxWidth: .infinity, maxHeight: .infinity) 63 | .position(x: geometry.size.width/2, y: geometry.size.height/2) 64 | } 65 | } 66 | 67 | #Preview("Floating Bar Chart") { 68 | let dataJson = """ 69 | { 70 | "labels": ["January", "February", "March", "April", "May", "June", "July"], 71 | "datasets": [ 72 | { 73 | "label": "Dataset 1", 74 | "data": [ 75 | [-65, 30], 76 | [-25, 59], 77 | [-30, 80], 78 | [-10, 81], 79 | [-25, 56], 80 | [-45, 55], 81 | [-15, 40] 82 | ], 83 | "backgroundColor": "rgba(255, 99, 132, 0.5)" 84 | }, 85 | { 86 | "label": "Dataset 2", 87 | "data": [ 88 | [-45, 28], 89 | [-80, 48], 90 | [-25, 40], 91 | [-60, 19], 92 | [-15, 86], 93 | [-50, 27], 94 | [-30, 90] 95 | ], 96 | "backgroundColor": "rgba(54, 162, 235, 0.5)" 97 | } 98 | ] 99 | } 100 | """ 101 | 102 | let configJson = """ 103 | { 104 | "type": "bar", 105 | "options": { 106 | "responsive": true, 107 | "plugins": { 108 | "legend": { 109 | "position": "top" 110 | }, 111 | "title": { 112 | "display": true, 113 | "text": "Chart.js Floating Bar Chart" 114 | } 115 | } 116 | } 117 | } 118 | """ 119 | 120 | return GeometryReader { geometry in 121 | Charts(dataJson: dataJson, configJson: configJson) 122 | .frame(height: geometry.size.width > geometry.size.height ? 123 | geometry.size.height : // landscape 124 | geometry.size.height * 0.3) // portrait 125 | .padding() 126 | .frame(maxWidth: .infinity, maxHeight: .infinity) 127 | .position(x: geometry.size.width/2, y: geometry.size.height/2) 128 | } 129 | } 130 | 131 | #Preview("Horizontal Bar Chart") { 132 | let dataJson = """ 133 | { 134 | "labels": ["January", "February", "March", "April", "May", "June", "July"], 135 | "datasets": [ 136 | { 137 | "label": "Dataset 1", 138 | "data": [65, -59, 80, -81, 56, -55, 40], 139 | "borderColor": "rgb(255, 99, 132)", 140 | "backgroundColor": "rgba(255, 99, 132, 0.5)", 141 | "borderWidth": 2 142 | }, 143 | { 144 | "label": "Dataset 2", 145 | "data": [28, -48, 40, -19, 86, -27, 90], 146 | "borderColor": "rgb(54, 162, 235)", 147 | "backgroundColor": "rgba(54, 162, 235, 0.5)", 148 | "borderWidth": 2 149 | } 150 | ] 151 | } 152 | """ 153 | 154 | let configJson = """ 155 | { 156 | "type": "bar", 157 | "options": { 158 | "indexAxis": "y", 159 | "elements": { 160 | "bar": { 161 | "borderWidth": 2 162 | } 163 | }, 164 | "responsive": true, 165 | "plugins": { 166 | "legend": { 167 | "position": "right" 168 | }, 169 | "title": { 170 | "display": true, 171 | "text": "Chart.js Horizontal Bar Chart" 172 | } 173 | } 174 | } 175 | } 176 | """ 177 | 178 | return GeometryReader { geometry in 179 | Charts(dataJson: dataJson, configJson: configJson) 180 | .frame(height: geometry.size.width > geometry.size.height ? 181 | geometry.size.height : // landscape 182 | geometry.size.height * 0.3) // portrait 183 | .padding() 184 | .frame(maxWidth: .infinity, maxHeight: .infinity) 185 | .position(x: geometry.size.width/2, y: geometry.size.height/2) 186 | } 187 | } 188 | 189 | #Preview("Stacked Bar Chart") { 190 | let dataJson = """ 191 | { 192 | "labels": ["January", "February", "March", "April", "May", "June", "July"], 193 | "datasets": [ 194 | { 195 | "label": "Dataset 1", 196 | "data": [65, -59, 80, -81, 56, -55, 40], 197 | "backgroundColor": "rgb(255, 99, 132)" 198 | }, 199 | { 200 | "label": "Dataset 2", 201 | "data": [28, -48, 40, -19, 86, -27, 90], 202 | "backgroundColor": "rgb(54, 162, 235)" 203 | }, 204 | { 205 | "label": "Dataset 3", 206 | "data": [35, -25, 60, -35, 45, -40, 70], 207 | "backgroundColor": "rgb(75, 192, 192)" 208 | } 209 | ] 210 | } 211 | """ 212 | 213 | let configJson = """ 214 | { 215 | "type": "bar", 216 | "options": { 217 | "plugins": { 218 | "title": { 219 | "display": true, 220 | "text": "Chart.js Bar Chart - Stacked" 221 | } 222 | }, 223 | "responsive": true, 224 | "scales": { 225 | "x": { 226 | "stacked": true 227 | }, 228 | "y": { 229 | "stacked": true 230 | } 231 | } 232 | } 233 | } 234 | """ 235 | 236 | return GeometryReader { geometry in 237 | Charts(dataJson: dataJson, configJson: configJson) 238 | .frame(height: geometry.size.width > geometry.size.height ? 239 | geometry.size.height : // landscape 240 | geometry.size.height * 0.3) // portrait 241 | .padding() 242 | .frame(maxWidth: .infinity, maxHeight: .infinity) 243 | .position(x: geometry.size.width/2, y: geometry.size.height/2) 244 | } 245 | } 246 | 247 | #Preview("Stacked Bar Chart with Groups") { 248 | let dataJson = """ 249 | { 250 | "labels": ["January", "February", "March", "April", "May", "June", "July"], 251 | "datasets": [ 252 | { 253 | "label": "Dataset 1", 254 | "data": [65, -59, 80, -81, 56, -55, 40], 255 | "backgroundColor": "rgb(255, 99, 132)", 256 | "stack": "Stack 0" 257 | }, 258 | { 259 | "label": "Dataset 2", 260 | "data": [28, -48, 40, -19, 86, -27, 90], 261 | "backgroundColor": "rgb(54, 162, 235)", 262 | "stack": "Stack 0" 263 | }, 264 | { 265 | "label": "Dataset 3", 266 | "data": [35, -25, 60, -35, 45, -40, 70], 267 | "backgroundColor": "rgb(75, 192, 192)", 268 | "stack": "Stack 1" 269 | } 270 | ] 271 | } 272 | """ 273 | 274 | let configJson = """ 275 | { 276 | "type": "bar", 277 | "options": { 278 | "plugins": { 279 | "title": { 280 | "display": true, 281 | "text": "Chart.js Bar Chart - Stacked with Groups" 282 | } 283 | }, 284 | "responsive": true, 285 | "interaction": { 286 | "intersect": false 287 | }, 288 | "scales": { 289 | "x": { 290 | "stacked": true 291 | }, 292 | "y": { 293 | "stacked": true 294 | } 295 | } 296 | } 297 | } 298 | """ 299 | 300 | return GeometryReader { geometry in 301 | Charts(dataJson: dataJson, configJson: configJson) 302 | .frame(height: geometry.size.width > geometry.size.height ? 303 | geometry.size.height : // landscape 304 | geometry.size.height * 0.3) // portrait 305 | .padding() 306 | .frame(maxWidth: .infinity, maxHeight: .infinity) 307 | .position(x: geometry.size.width/2, y: geometry.size.height/2) 308 | } 309 | } 310 | 311 | #Preview("Vertical Bar Chart") { 312 | let dataJson = """ 313 | { 314 | "labels": ["January", "February", "March", "April", "May", "June", "July"], 315 | "datasets": [ 316 | { 317 | "label": "Dataset 1", 318 | "data": [65, -59, 80, -81, 56, -55, 40], 319 | "borderColor": "rgb(255, 99, 132)", 320 | "backgroundColor": "rgba(255, 99, 132, 0.5)" 321 | }, 322 | { 323 | "label": "Dataset 2", 324 | "data": [28, -48, 40, -19, 86, -27, 90], 325 | "borderColor": "rgb(54, 162, 235)", 326 | "backgroundColor": "rgba(54, 162, 235, 0.5)" 327 | } 328 | ] 329 | } 330 | """ 331 | 332 | let configJson = """ 333 | { 334 | "type": "bar", 335 | "options": { 336 | "responsive": true, 337 | "plugins": { 338 | "legend": { 339 | "position": "top" 340 | }, 341 | "title": { 342 | "display": true, 343 | "text": "Chart.js Bar Chart" 344 | } 345 | } 346 | } 347 | } 348 | """ 349 | 350 | return GeometryReader { geometry in 351 | Charts(dataJson: dataJson, configJson: configJson) 352 | .frame(height: geometry.size.width > geometry.size.height ? 353 | geometry.size.height : // landscape 354 | geometry.size.height * 0.3) // portrait 355 | .padding() 356 | .frame(maxWidth: .infinity, maxHeight: .infinity) 357 | .position(x: geometry.size.width/2, y: geometry.size.height/2) 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /ChartJS Demo/ChartJS Demo/Charts/Line.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Line.swift 3 | // ChartJS 4 | // 5 | // Created by Ming on 21/5/2025. 6 | // 7 | 8 | import SwiftUI 9 | import ChartJS 10 | 11 | #Preview("Simple Line Chart") { 12 | let dataJson = """ 13 | { 14 | "labels": ["January", "February", "March", "April", "May", "June", "July"], 15 | "datasets": [{ 16 | "label": "My First Dataset", 17 | "data": [65, 59, 80, 81, 56, 55, 40], 18 | "fill": false, 19 | "borderColor": "rgb(75, 192, 192)", 20 | "tension": 0.3 21 | }] 22 | } 23 | """ 24 | 25 | let configJson = """ 26 | { 27 | "type": "line", 28 | "options": { 29 | "responsive": true, 30 | "plugins": { 31 | "title": { 32 | "display": true, 33 | "text": "Chart.js Line Chart" 34 | } 35 | } 36 | } 37 | } 38 | """ 39 | 40 | GeometryReader { geometry in 41 | Charts(dataJson: dataJson, configJson: configJson) 42 | .frame(height: geometry.size.width > geometry.size.height ? 43 | geometry.size.height : // landscape 44 | geometry.size.height * 0.3) // portrait 45 | .padding() 46 | .frame(maxWidth: .infinity, maxHeight: .infinity) 47 | .position(x: geometry.size.width/2, y: geometry.size.height/2) 48 | } 49 | } 50 | 51 | #Preview("Line Chart with Gradient") { 52 | let dataJson = """ 53 | { 54 | "labels": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], 55 | "datasets": [{ 56 | "label": "Monthly Data", 57 | "data": [45, 55, 65, 75, 95, 105, 95, 75, 55, 45, 35, 25], 58 | "fill": true, 59 | "borderColor": "rgb(75, 192, 192)", 60 | "tension": 0.35, 61 | "backgroundColor": "GRADIENT" 62 | }] 63 | } 64 | """ 65 | 66 | let configJson = """ 67 | { 68 | "type": "line", 69 | "options": { 70 | "responsive": true, 71 | "plugins": { 72 | "title": { 73 | "display": true, 74 | "text": "Chart.js Line Chart with Vertical Gradient" 75 | } 76 | }, 77 | "scales": { 78 | "y": { 79 | "beginAtZero": true 80 | } 81 | } 82 | } 83 | } 84 | """ 85 | 86 | let scriptSetup = """ 87 | 106 | """ 107 | 108 | GeometryReader { geometry in 109 | Charts(dataJson: dataJson, configJson: configJson, scriptSetup: scriptSetup) 110 | .frame(height: geometry.size.width > geometry.size.height ? 111 | geometry.size.height : // landscape 112 | geometry.size.height * 0.3) // portrait 113 | .padding() 114 | .frame(maxWidth: .infinity, maxHeight: .infinity) 115 | .position(x: geometry.size.width/2, y: geometry.size.height/2) 116 | } 117 | } 118 | 119 | #Preview("Multi-Dataset Line Chart with Gradients") { 120 | let dataJson = """ 121 | { 122 | "labels": ["January", "February", "March", "April", "May", "June", "July"], 123 | "datasets": [ 124 | { 125 | "label": "Dataset 1", 126 | "data": [65, 59, 80, 81, 56, 55, 40], 127 | "borderColor": "rgb(255, 99, 132)", 128 | "backgroundColor": "GRADIENT1", 129 | "tension": 0.3, 130 | "fill": true 131 | }, 132 | { 133 | "label": "Dataset 2", 134 | "data": [28, 48, 40, 19, 45, 35, 30], 135 | "borderColor": "rgb(35, 253, 152)", 136 | "backgroundColor": "GRADIENT2", 137 | "tension": 0.3, 138 | "fill": true 139 | } 140 | ] 141 | } 142 | """ 143 | 144 | let configJson = """ 145 | { 146 | "type": "line", 147 | "options": { 148 | "responsive": true, 149 | "plugins": { 150 | "title": { 151 | "display": true, 152 | "text": "Chart.js Multi-Line Chart with Gradients" 153 | } 154 | } 155 | } 156 | } 157 | """ 158 | 159 | let scriptSetup = """ 160 | 187 | """ 188 | 189 | GeometryReader { geometry in 190 | Charts(dataJson: dataJson, configJson: configJson, scriptSetup: scriptSetup) 191 | .frame(height: geometry.size.width > geometry.size.height ? 192 | geometry.size.height : // landscape 193 | geometry.size.height * 0.3) // portrait 194 | .padding() 195 | .frame(maxWidth: .infinity, maxHeight: .infinity) 196 | .position(x: geometry.size.width/2, y: geometry.size.height/2) 197 | } 198 | } 199 | 200 | #Preview("Stacked Bar/Line Chart") { 201 | let dataJson = """ 202 | { 203 | "labels": ["January", "February", "March", "April", "May", "June", "July"], 204 | "datasets": [ 205 | { 206 | "label": "Dataset 1", 207 | "data": [65, 59, 80, 81, 56, 55, 40], 208 | "borderColor": "rgb(255, 99, 132)", 209 | "backgroundColor": "rgba(255, 99, 132, 0.5)", 210 | "stack": "combined", 211 | "type": "bar" 212 | }, 213 | { 214 | "label": "Dataset 2", 215 | "data": [28, 48, 40, 19, 86, 27, 90], 216 | "borderColor": "rgb(54, 162, 235)", 217 | "backgroundColor": "rgba(54, 162, 235, 0.5)", 218 | "stack": "combined" 219 | } 220 | ] 221 | } 222 | """ 223 | 224 | let configJson = """ 225 | { 226 | "type": "line", 227 | "options": { 228 | "plugins": { 229 | "title": { 230 | "display": true, 231 | "text": "Chart.js Stacked Line/Bar Chart" 232 | } 233 | }, 234 | "scales": { 235 | "y": { 236 | "stacked": true 237 | } 238 | } 239 | } 240 | } 241 | """ 242 | 243 | return GeometryReader { geometry in 244 | Charts(dataJson: dataJson, configJson: configJson) 245 | .frame(height: geometry.size.width > geometry.size.height ? 246 | geometry.size.height : // landscape 247 | geometry.size.height * 0.3) // portrait 248 | .padding() 249 | .frame(maxWidth: .infinity, maxHeight: .infinity) 250 | .position(x: geometry.size.width/2, y: geometry.size.height/2) 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /ChartJS Demo/ChartJS Demo/Charts/Pie.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Pie.swift 3 | // ChartJS 4 | // 5 | // Created by Ming on 21/5/2025. 6 | // 7 | 8 | import SwiftUI 9 | import ChartJS 10 | 11 | #Preview("Pie Chart") { 12 | let dataJson = """ 13 | { 14 | "labels": ["Red", "Orange", "Yellow", "Green", "Blue"], 15 | "datasets": [ 16 | { 17 | "label": "Dataset 1", 18 | "data": [65, 59, 80, 81, 56], 19 | "backgroundColor": [ 20 | "rgb(255, 99, 132)", 21 | "rgb(255, 159, 64)", 22 | "rgb(255, 205, 86)", 23 | "rgb(75, 192, 192)", 24 | "rgb(54, 162, 235)" 25 | ] 26 | } 27 | ] 28 | } 29 | """ 30 | 31 | let configJson = """ 32 | { 33 | "type": "pie", 34 | "options": { 35 | "responsive": true, 36 | "plugins": { 37 | "legend": { 38 | "position": "top" 39 | }, 40 | "title": { 41 | "display": true, 42 | "text": "Chart.js Pie Chart" 43 | } 44 | } 45 | } 46 | } 47 | """ 48 | 49 | return GeometryReader { geometry in 50 | Charts(dataJson: dataJson, configJson: configJson) 51 | .frame(height: geometry.size.width > geometry.size.height ? 52 | geometry.size.height : // landscape 53 | geometry.size.height * 0.3) // portrait 54 | .padding() 55 | .frame(maxWidth: .infinity, maxHeight: .infinity) 56 | .position(x: geometry.size.width/2, y: geometry.size.height/2) 57 | } 58 | } 59 | 60 | #Preview("Doughnut Chart") { 61 | let dataJson = """ 62 | { 63 | "labels": ["Red", "Orange", "Yellow", "Green", "Blue"], 64 | "datasets": [ 65 | { 66 | "label": "Dataset 1", 67 | "data": [65, 59, 80, 81, 56], 68 | "backgroundColor": [ 69 | "rgb(255, 99, 132)", 70 | "rgb(255, 159, 64)", 71 | "rgb(255, 205, 86)", 72 | "rgb(75, 192, 192)", 73 | "rgb(54, 162, 235)" 74 | ] 75 | } 76 | ] 77 | } 78 | """ 79 | 80 | let configJson = """ 81 | { 82 | "type": "doughnut", 83 | "options": { 84 | "responsive": true, 85 | "plugins": { 86 | "legend": { 87 | "position": "top" 88 | }, 89 | "title": { 90 | "display": true, 91 | "text": "Chart.js Doughnut Chart" 92 | } 93 | } 94 | } 95 | } 96 | """ 97 | 98 | return GeometryReader { geometry in 99 | Charts(dataJson: dataJson, configJson: configJson) 100 | .frame(height: geometry.size.width > geometry.size.height ? 101 | geometry.size.height : // landscape 102 | geometry.size.height * 0.3) // portrait 103 | .padding() 104 | .frame(maxWidth: .infinity, maxHeight: .infinity) 105 | .position(x: geometry.size.width/2, y: geometry.size.height/2) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /ChartJS Demo/ChartJS Demo/Charts/Polar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Polar.swift 3 | // ChartJS 4 | // 5 | // Created by Ming on 21/5/2025. 6 | // 7 | 8 | import SwiftUI 9 | import ChartJS 10 | 11 | #Preview("Polar Area Chart") { 12 | let dataJson = """ 13 | { 14 | "labels": ["Red", "Orange", "Yellow", "Green", "Blue"], 15 | "datasets": [ 16 | { 17 | "label": "Dataset 1", 18 | "data": [65, 59, 80, 81, 56], 19 | "backgroundColor": [ 20 | "rgba(255, 99, 132, 0.5)", 21 | "rgba(255, 159, 64, 0.5)", 22 | "rgba(255, 205, 86, 0.5)", 23 | "rgba(75, 192, 192, 0.5)", 24 | "rgba(54, 162, 235, 0.5)" 25 | ] 26 | } 27 | ] 28 | } 29 | """ 30 | 31 | let configJson = """ 32 | { 33 | "type": "polarArea", 34 | "options": { 35 | "responsive": true, 36 | "scales": { 37 | "r": { 38 | "pointLabels": { 39 | "display": true, 40 | "centerPointLabels": true, 41 | "font": { 42 | "size": 18 43 | } 44 | } 45 | } 46 | }, 47 | "plugins": { 48 | "legend": { 49 | "position": "top" 50 | }, 51 | "title": { 52 | "display": true, 53 | "text": "Chart.js Polar Area Chart With Centered Point Labels" 54 | } 55 | } 56 | } 57 | } 58 | """ 59 | 60 | return GeometryReader { geometry in 61 | Charts(dataJson: dataJson, configJson: configJson) 62 | .frame(height: geometry.size.width > geometry.size.height ? 63 | geometry.size.height : // landscape 64 | geometry.size.height * 0.5) // portrait 65 | .padding() 66 | .frame(maxWidth: .infinity, maxHeight: .infinity) 67 | .position(x: geometry.size.width/2, y: geometry.size.height/2) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /ChartJS Demo/ChartJS Demo/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // ChartJS Demo 4 | // 5 | // Created by Ming on 21/5/2025. 6 | // 7 | 8 | import SwiftUI 9 | import ChartJS 10 | 11 | #Preview("Simple Chart") { 12 | // Use the entire view for the chart 13 | let dataJson = """ 14 | { 15 | "labels": ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"], 16 | "datasets": [{ 17 | "label": "Data", 18 | "data": [12, 19, 3, 5, 2, 3], 19 | "backgroundColor": "rgba(75, 192, 192, 0.2)", 20 | "borderColor": "rgba(75, 192, 192, 1)", 21 | "borderWidth": 1 22 | }] 23 | } 24 | """ 25 | 26 | let configJson = """ 27 | { 28 | "type": "bar", 29 | "options": { 30 | "responsive": true, 31 | "plugins": { 32 | "title": { 33 | "display": true, 34 | "text": "Simple Chart Example" 35 | } 36 | }, 37 | "scales": { 38 | "y": { 39 | "beginAtZero": true 40 | } 41 | } 42 | } 43 | } 44 | """ 45 | 46 | return GeometryReader { geometry in 47 | Charts(dataJson: dataJson, configJson: configJson) 48 | .frame(height: geometry.size.width > geometry.size.height ? 49 | geometry.size.height : // landscape 50 | geometry.size.height * 0.3) // portrait 51 | .padding() 52 | .frame(maxWidth: .infinity, maxHeight: .infinity) 53 | .position(x: geometry.size.width/2, y: geometry.size.height/2) 54 | } 55 | } 56 | 57 | #Preview("Multiple Datasets") { 58 | // Use the entire view for the chart 59 | let dataJson = """ 60 | { 61 | "labels": ["Jan", "Feb", "Mar", "Apr", "May", "Jun"], 62 | "datasets": [ 63 | { 64 | "label": "Sales", 65 | "data": [12, 19, 3, 5, 2, 3], 66 | "backgroundColor": "rgba(255, 99, 132, 0.2)", 67 | "borderColor": "rgba(255, 99, 132, 1)", 68 | "borderWidth": 1 69 | }, 70 | { 71 | "label": "Revenue", 72 | "data": [7, 11, 5, 8, 3, 7], 73 | "backgroundColor": "rgba(54, 162, 235, 0.2)", 74 | "borderColor": "rgba(54, 162, 235, 1)", 75 | "borderWidth": 1 76 | } 77 | ] 78 | } 79 | """ 80 | 81 | let configJson = """ 82 | { 83 | "type": "bar", 84 | "options": { 85 | "responsive": true, 86 | "plugins": { 87 | "title": { 88 | "display": true, 89 | "text": "Dynamic Chart Example" 90 | } 91 | }, 92 | "scales": { 93 | "y": { 94 | "beginAtZero": true 95 | } 96 | } 97 | } 98 | } 99 | """ 100 | 101 | return GeometryReader { geometry in 102 | Charts(dataJson: dataJson, configJson: configJson) 103 | .frame(height: geometry.size.width > geometry.size.height ? 104 | geometry.size.height : // landscape 105 | geometry.size.height * 0.3) // portrait 106 | .padding() 107 | .frame(maxWidth: .infinity, maxHeight: .infinity) 108 | .position(x: geometry.size.width/2, y: geometry.size.height/2) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /ChartJS Demo/ChartJS DemoTests/ChartJS_DemoTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChartJS_DemoTests.swift 3 | // ChartJS DemoTests 4 | // 5 | // Created by Ming on 21/5/2025. 6 | // 7 | 8 | import Testing 9 | 10 | struct ChartJS_DemoTests { 11 | 12 | @Test func example() async throws { 13 | // Write your test here and use APIs like `#expect(...)` to check expected conditions. 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /ChartJS Demo/ChartJS DemoUITests/ChartJS_DemoUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChartJS_DemoUITests.swift 3 | // ChartJS DemoUITests 4 | // 5 | // Created by Ming on 21/5/2025. 6 | // 7 | 8 | import XCTest 9 | 10 | final class ChartJS_DemoUITests: 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 | @MainActor 26 | func testExample() throws { 27 | // UI tests must launch the application that they test. 28 | let app = XCUIApplication() 29 | app.launch() 30 | 31 | // Use XCTAssert and related functions to verify your tests produce the correct results. 32 | } 33 | 34 | @MainActor 35 | func testLaunchPerformance() throws { 36 | // This measures how long it takes to launch your application. 37 | measure(metrics: [XCTApplicationLaunchMetric()]) { 38 | XCUIApplication().launch() 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ChartJS Demo/ChartJS DemoUITests/ChartJS_DemoUITestsLaunchTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChartJS_DemoUITestsLaunchTests.swift 3 | // ChartJS DemoUITests 4 | // 5 | // Created by Ming on 21/5/2025. 6 | // 7 | 8 | import XCTest 9 | 10 | final class ChartJS_DemoUITestsLaunchTests: XCTestCase { 11 | 12 | override class var runsForEachTargetApplicationUIConfiguration: Bool { 13 | true 14 | } 15 | 16 | override func setUpWithError() throws { 17 | continueAfterFailure = false 18 | } 19 | 20 | @MainActor 21 | func testLaunch() throws { 22 | let app = XCUIApplication() 23 | app.launch() 24 | 25 | // Insert steps here to perform after app launch but before taking a screenshot, 26 | // such as logging into a test account or navigating somewhere in the app 27 | 28 | let attachment = XCTAttachment(screenshot: app.screenshot()) 29 | attachment.name = "Launch Screen" 30 | attachment.lifetime = .keepAlways 31 | add(attachment) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 MING 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 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 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: "ChartJS", 8 | platforms: [ 9 | .iOS(.v13), 10 | // .macOS(.v10_15), .tvOS(.v13), .watchOS(.v6) 11 | ], 12 | products: [ 13 | // Products define the executables and libraries a package produces, making them visible to other packages. 14 | .library( 15 | name: "ChartJS", 16 | targets: ["ChartJS"]), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package, defining a module or a test suite. 20 | // Targets can depend on other targets in this package and products from dependencies. 21 | .target( 22 | name: "ChartJS", 23 | resources: [ 24 | .process("Resources") 25 | ]), 26 | // .testTarget( 27 | // name: "ChartJSTests", 28 | // dependencies: ["ChartJS"] 29 | // ), 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChartJS for Swift 2 | 3 | A SwiftUI wrapper for Chart.js that allows you to easily create beautiful, responsive charts in your iOS ~~and macOS~~ applications. 4 | 5 | ![ChartJS@3x](https://github.com/user-attachments/assets/9051153e-7d07-4b4f-8da7-fa9ac1793349) 6 | 7 | ## Features 8 | 9 | - Create various chart types supported by Chart.js (bar, line, pie, doughnut, etc.) 10 | - Fully customizable with Chart.js configuration options 11 | - Works on iOS and macOS (fallback message for tvOS and watchOS) 12 | - SwiftUI integration with `Charts` view 13 | - Responsive design that adapts to different screen sizes 14 | 15 | ## Requirements 16 | 17 | - iOS 13.0+ / ~~macOS 10.15+~~(Coming Soon) 18 | - Swift 5.9+ 19 | - Xcode 16.0+ 20 | 21 | ## Installation 22 | 23 | ### Swift Package Manager 24 | 25 | Add this package to your project by adding the following to your `Package.swift` file: 26 | 27 | ```swift 28 | dependencies: [ 29 | .package(url: "https://github.com/1998code/ChartJS-for-Swift", from: "1.0.0") 30 | ] 31 | ``` 32 | 33 | Or add it directly from Xcode: 34 | 1. File > Swift Packages > Add Package Dependency 35 | 2. Enter the repository URL: `https://github.com/1998code/ChartJS-for-Swift` 36 | 37 | ## Usage 38 | 39 | ### Basic Example 40 | 41 | ```swift 42 | import SwiftUI 43 | import ChartJS 44 | 45 | struct ContentView: View { 46 | let dataJson = """ 47 | { 48 | "labels": ["Jan", "Feb", "Mar", "Apr", "May", "Jun"], 49 | "datasets": [{ 50 | "label": "Sales 2025", 51 | "data": [12, 19, 3, 5, 2, 3], 52 | "backgroundColor": "rgba(75, 192, 192, 0.2)", 53 | "borderColor": "rgba(75, 192, 192, 1)", 54 | "borderWidth": 1 55 | }] 56 | } 57 | """ 58 | 59 | let configJson = """ 60 | { 61 | "type": "bar", 62 | "options": { 63 | "responsive": true, 64 | "plugins": { 65 | "title": { 66 | "display": true, 67 | "text": "Monthly Sales" 68 | } 69 | }, 70 | "scales": { 71 | "y": { 72 | "beginAtZero": true 73 | } 74 | } 75 | } 76 | } 77 | """ 78 | 79 | var body: some View { 80 | VStack { 81 | Charts(dataJson: dataJson, configJson: configJson) 82 | .frame(height: 300) 83 | .padding() 84 | 85 | Text("Monthly Sales Chart") 86 | .font(.caption) 87 | } 88 | } 89 | } 90 | ``` 91 | 92 | ### Multiple Chart Types 93 | 94 | You can create different chart types by changing the `type` property in the configuration: 95 | 96 | ```swift 97 | // Line chart 98 | let lineConfigJson = """ 99 | { 100 | "type": "line", 101 | "options": { 102 | "responsive": true, 103 | "plugins": { 104 | "title": { 105 | "display": true, 106 | "text": "Line Chart" 107 | } 108 | } 109 | } 110 | } 111 | """ 112 | 113 | // Pie chart 114 | let pieConfigJson = """ 115 | { 116 | "type": "pie", 117 | "options": { 118 | "responsive": true, 119 | "plugins": { 120 | "title": { 121 | "display": true, 122 | "text": "Pie Chart" 123 | } 124 | } 125 | } 126 | } 127 | """ 128 | ``` 129 | 130 | ### Responsive Layout 131 | 132 | For the best responsive experience, use GeometryReader: 133 | 134 | ```swift 135 | GeometryReader { geometry in 136 | Charts(dataJson: dataJson, configJson: configJson) 137 | .frame(height: geometry.size.width > geometry.size.height ? 138 | geometry.size.height : // landscape 139 | geometry.size.height * 0.3) // portrait 140 | .padding() 141 | } 142 | ``` 143 | 144 | ### Custom JavaScript 145 | 146 | If you need to add custom JavaScript initialization, use the `scriptSetup` parameter: 147 | 148 | ```swift 149 | let scriptSetup = """ 150 | 157 | """ 158 | 159 | Charts(dataJson: dataJson, configJson: configJson, scriptSetup: scriptSetup) 160 | ``` 161 | 162 | ## Platform Support 163 | 164 | - **iOS**: Fully supported 165 | - **macOS**: Coming Soon 166 | - **tvOS**: (WebKit not supported) 167 | - **watchOS**: (WebKit not supported) 168 | 169 | ## Troubleshooting 170 | 171 | If you encounter any issues: 172 | 173 | 1. Make sure your JSON is valid 174 | 2. Check that your data structure matches what Chart.js expects 175 | 3. For complex charts, test your configuration on the Chart.js website first 176 | 177 | ## License 178 | 179 | This project is available under the MIT license. See the LICENSE file for more info. 180 | 181 | ## Acknowledgements 182 | 183 | - [Chart.js](https://www.chartjs.org/) - Simple yet flexible JavaScript charting library 184 | 185 | --- 186 | 187 | ## Translations 188 | 189 | This doc is also available in: 190 | 191 | [English](README.md) | [繁中](README/README.zh-TW.md) / [简中](README/README.zh-CN.md) / [粵語](README/README.zh-HK.md) | [日本語](README/README.ja.md) | [Español](README/README.es.md) 192 | 193 | Please feel free to open a pull request and add new language(s) or fix any typos/mistakes. 194 | -------------------------------------------------------------------------------- /README/README.es.md: -------------------------------------------------------------------------------- 1 | # ChartJS para Swift 2 | 3 | Un envoltorio SwiftUI para Chart.js que te permite crear fácilmente gráficos hermosos y responsivos en tus aplicaciones iOS ~~y macOS~~. 4 | 5 | ![ChartJS@3x](https://github.com/user-attachments/assets/9051153e-7d07-4b4f-8da7-fa9ac1793349) 6 | 7 | ## Características 8 | 9 | - Crea varios tipos de gráficos soportados por Chart.js (barras, líneas, circular, anillo, etc.) 10 | - Completamente personalizable con opciones de configuración de Chart.js 11 | - Funciona en iOS y macOS (mensaje alternativo para tvOS y watchOS) 12 | - Integración con SwiftUI mediante la vista `Charts` 13 | - Diseño responsivo que se adapta a diferentes tamaños de pantalla 14 | 15 | ## Requisitos 16 | 17 | - iOS 13.0+ / ~~macOS 10.15+~~(Próximamente) 18 | - Swift 5.9+ 19 | - Xcode 16.0+ 20 | 21 | ## Instalación 22 | 23 | ### Swift Package Manager 24 | 25 | Añade este paquete a tu proyecto agregando lo siguiente a tu archivo `Package.swift`: 26 | 27 | ```swift 28 | dependencies: [ 29 | .package(url: "https://github.com/1998code/ChartJS-for-Swift", from: "1.0.0") 30 | ] 31 | ``` 32 | 33 | O añádelo directamente desde Xcode: 34 | 1. Archivo > Swift Packages > Añadir dependencia de paquete 35 | 2. Introduce la URL del repositorio: `https://github.com/1998code/ChartJS-for-Swift` 36 | 37 | ## Uso 38 | 39 | ### Ejemplo básico 40 | 41 | ```swift 42 | import SwiftUI 43 | import ChartJS 44 | 45 | struct ContentView: View { 46 | let dataJson = """ 47 | { 48 | "labels": ["Ene", "Feb", "Mar", "Abr", "May", "Jun"], 49 | "datasets": [{ 50 | "label": "Ventas 2025", 51 | "data": [12, 19, 3, 5, 2, 3], 52 | "backgroundColor": "rgba(75, 192, 192, 0.2)", 53 | "borderColor": "rgba(75, 192, 192, 1)", 54 | "borderWidth": 1 55 | }] 56 | } 57 | """ 58 | 59 | let configJson = """ 60 | { 61 | "type": "bar", 62 | "options": { 63 | "responsive": true, 64 | "plugins": { 65 | "title": { 66 | "display": true, 67 | "text": "Ventas Mensuales" 68 | } 69 | }, 70 | "scales": { 71 | "y": { 72 | "beginAtZero": true 73 | } 74 | } 75 | } 76 | } 77 | """ 78 | 79 | var body: some View { 80 | VStack { 81 | Charts(dataJson: dataJson, configJson: configJson) 82 | .frame(height: 300) 83 | .padding() 84 | 85 | Text("Gráfico de ventas mensuales") 86 | .font(.caption) 87 | } 88 | } 89 | } 90 | ``` 91 | 92 | ### Múltiples tipos de gráficos 93 | 94 | Puedes crear diferentes tipos de gráficos cambiando la propiedad `type` en la configuración: 95 | 96 | ```swift 97 | // Gráfico de líneas 98 | let lineConfigJson = """ 99 | { 100 | "type": "line", 101 | "options": { 102 | "responsive": true, 103 | "plugins": { 104 | "title": { 105 | "display": true, 106 | "text": "Gráfico de Líneas" 107 | } 108 | } 109 | } 110 | } 111 | """ 112 | 113 | // Gráfico circular 114 | let pieConfigJson = """ 115 | { 116 | "type": "pie", 117 | "options": { 118 | "responsive": true, 119 | "plugins": { 120 | "title": { 121 | "display": true, 122 | "text": "Gráfico Circular" 123 | } 124 | } 125 | } 126 | } 127 | """ 128 | ``` 129 | 130 | ### Diseño responsivo 131 | 132 | Para la mejor experiencia responsiva, usa GeometryReader: 133 | 134 | ```swift 135 | GeometryReader { geometry in 136 | Charts(dataJson: dataJson, configJson: configJson) 137 | .frame(height: geometry.size.width > geometry.size.height ? 138 | geometry.size.height : // paisaje 139 | geometry.size.height * 0.3) // retrato 140 | .padding() 141 | } 142 | ``` 143 | 144 | ### JavaScript personalizado 145 | 146 | Si necesitas añadir inicialización JavaScript personalizada, usa el parámetro `scriptSetup`: 147 | 148 | ```swift 149 | let scriptSetup = """ 150 | 157 | """ 158 | 159 | Charts(dataJson: dataJson, configJson: configJson, scriptSetup: scriptSetup) 160 | ``` 161 | 162 | ## Soporte de plataformas 163 | 164 | - **iOS**: Totalmente soportado 165 | - **macOS**: Próximamente 166 | - **tvOS**: (WebKit no soportado) 167 | - **watchOS**: (WebKit no soportado) 168 | 169 | ## Solución de problemas 170 | 171 | Si encuentras problemas: 172 | 173 | 1. Asegúrate de que tu JSON sea válido 174 | 2. Comprueba que tu estructura de datos coincida con lo que Chart.js espera 175 | 3. Para gráficos complejos, prueba tu configuración en el sitio web de Chart.js primero 176 | 177 | ## Licencia 178 | 179 | Este proyecto está disponible bajo la licencia MIT. Consulta el archivo LICENSE para más información. 180 | 181 | ## Agradecimientos 182 | 183 | - [Chart.js](https://www.chartjs.org/) - Librería JavaScript de gráficos simple y flexible 184 | 185 | --- 186 | 187 | ## Traducciones 188 | 189 | Esta documentación está disponible también en: 190 | 191 | [English](../README.md) | [繁中](README.zh-TW.md) / [简中](README.zh-CN.md) / [粵語](README.zh-HK.md) | [日本語](README.ja.md) | [Español](README.es.md) 192 | 193 | No dudes en abrir un pull request para añadir nuevos idiomas o corregir cualquier error. -------------------------------------------------------------------------------- /README/README.ja.md: -------------------------------------------------------------------------------- 1 | # ChartJS for Swift 2 | 3 | Chart.js のための SwiftUI ラッパーで、iOS ~~および macOS~~ アプリケーションで美しいレスポンシブチャートを簡単に作成できます。 4 | 5 | ![ChartJS@3x](https://github.com/user-attachments/assets/9051153e-7d07-4b4f-8da7-fa9ac1793349) 6 | 7 | ## 特徴 8 | 9 | - Chart.js がサポートする様々なチャートタイプ(棒グラフ、折れ線グラフ、円グラフ、ドーナツグラフなど)を作成可能 10 | - Chart.js の設定オプションで完全にカスタマイズ可能 11 | - iOS と macOS で動作(tvOS と watchOS はフォールバックメッセージを表示) 12 | - `Charts` ビューによる SwiftUI 統合 13 | - 様々な画面サイズに適応するレスポンシブデザイン 14 | 15 | ## 要件 16 | 17 | - iOS 13.0+ / ~~macOS 10.15+~~(近日公開) 18 | - Swift 5.9+ 19 | - Xcode 16.0+ 20 | 21 | ## インストール 22 | 23 | ### Swift Package Manager 24 | 25 | `Package.swift` ファイルに以下を追加して、このパッケージをプロジェクトに追加します: 26 | 27 | ```swift 28 | dependencies: [ 29 | .package(url: "https://github.com/1998code/ChartJS-for-Swift", from: "1.0.0") 30 | ] 31 | ``` 32 | 33 | または Xcode から直接追加することもできます: 34 | 1. ファイル > Swift Packages > パッケージ依存関係を追加 35 | 2. リポジトリ URL を入力:`https://github.com/1998code/ChartJS-for-Swift` 36 | 37 | ## 使用方法 38 | 39 | ### 基本的な例 40 | 41 | ```swift 42 | import SwiftUI 43 | import ChartJS 44 | 45 | struct ContentView: View { 46 | let dataJson = """ 47 | { 48 | "labels": ["1月", "2月", "3月", "4月", "5月", "6月"], 49 | "datasets": [{ 50 | "label": "2025年の売上", 51 | "data": [12, 19, 3, 5, 2, 3], 52 | "backgroundColor": "rgba(75, 192, 192, 0.2)", 53 | "borderColor": "rgba(75, 192, 192, 1)", 54 | "borderWidth": 1 55 | }] 56 | } 57 | """ 58 | 59 | let configJson = """ 60 | { 61 | "type": "bar", 62 | "options": { 63 | "responsive": true, 64 | "plugins": { 65 | "title": { 66 | "display": true, 67 | "text": "月次売上" 68 | } 69 | }, 70 | "scales": { 71 | "y": { 72 | "beginAtZero": true 73 | } 74 | } 75 | } 76 | } 77 | """ 78 | 79 | var body: some View { 80 | VStack { 81 | Charts(dataJson: dataJson, configJson: configJson) 82 | .frame(height: 300) 83 | .padding() 84 | 85 | Text("月次売上チャート") 86 | .font(.caption) 87 | } 88 | } 89 | } 90 | ``` 91 | 92 | ### 複数のチャートタイプ 93 | 94 | 設定の `type` プロパティを変更することで、異なるチャートタイプを作成できます: 95 | 96 | ```swift 97 | // 折れ線グラフ 98 | let lineConfigJson = """ 99 | { 100 | "type": "line", 101 | "options": { 102 | "responsive": true, 103 | "plugins": { 104 | "title": { 105 | "display": true, 106 | "text": "折れ線グラフ" 107 | } 108 | } 109 | } 110 | } 111 | """ 112 | 113 | // 円グラフ 114 | let pieConfigJson = """ 115 | { 116 | "type": "pie", 117 | "options": { 118 | "responsive": true, 119 | "plugins": { 120 | "title": { 121 | "display": true, 122 | "text": "円グラフ" 123 | } 124 | } 125 | } 126 | } 127 | """ 128 | ``` 129 | 130 | ### レスポンシブレイアウト 131 | 132 | 最適なレスポンシブ体験を得るには、GeometryReaderを使用します: 133 | 134 | ```swift 135 | GeometryReader { geometry in 136 | Charts(dataJson: dataJson, configJson: configJson) 137 | .frame(height: geometry.size.width > geometry.size.height ? 138 | geometry.size.height : // 横向き 139 | geometry.size.height * 0.3) // 縦向き 140 | .padding() 141 | } 142 | ``` 143 | 144 | ### カスタムJavaScript 145 | 146 | カスタムJavaScript初期化を追加する必要がある場合は、`scriptSetup`パラメータを使用します: 147 | 148 | ```swift 149 | let scriptSetup = """ 150 | 157 | """ 158 | 159 | Charts(dataJson: dataJson, configJson: configJson, scriptSetup: scriptSetup) 160 | ``` 161 | 162 | ## プラットフォームサポート 163 | 164 | - **iOS**:完全対応 165 | - **macOS**:近日公開 166 | - **tvOS**:(WebKit非対応) 167 | - **watchOS**:(WebKit非対応) 168 | 169 | ## トラブルシューティング 170 | 171 | 問題が発生した場合: 172 | 173 | 1. JSONが有効であることを確認する 174 | 2. データ構造がChart.jsの期待するものと一致していることを確認する 175 | 3. 複雑なチャートの場合は、先にChart.jsのウェブサイトで設定をテストする 176 | 177 | ## ライセンス 178 | 179 | このプロジェクトはMITライセンスの下で利用可能です。詳細はLICENSEファイルをご覧ください。 180 | 181 | ## 謝辞 182 | 183 | - [Chart.js](https://www.chartjs.org/) - シンプルで柔軟なJavaScriptチャートライブラリ 184 | 185 | --- 186 | 187 | ## 翻訳 188 | 189 | このドキュメントは以下の言語でもご利用いただけます: 190 | 191 | [English](../README.md) | [繁中](README.zh-TW.md) / [简中](README.zh-CN.md) / [粵語](README.zh-HK.md) | [日本語](README.ja.md) | [Español](README.es.md) 192 | 193 | 新しい言語の追加や誤字・間違いの修正は、プルリクエストでお気軽にご貢献ください。 -------------------------------------------------------------------------------- /README/README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # ChartJS for Swift 2 | 3 | Chart.js 的 SwiftUI 封装,让您可以在 iOS ~~和 macOS~~ 应用程序中轻松创建美观、响应式的图表。 4 | 5 | ![ChartJS@3x](https://github.com/user-attachments/assets/9051153e-7d07-4b4f-8da7-fa9ac1793349) 6 | 7 | ## 功能特点 8 | 9 | - 支持 Chart.js 提供的各种图表类型(条形图、折线图、饼图、环形图等) 10 | - 完全可定制的 Chart.js 配置选项 11 | - 适用于 iOS 和 macOS(tvOS 和 watchOS 提供回退消息) 12 | - 与 `Charts` 视图的 SwiftUI 集成 13 | - 响应式设计,可适应不同屏幕尺寸 14 | 15 | ## 系统要求 16 | 17 | - iOS 13.0+ / ~~macOS 10.15+~~(即将推出) 18 | - Swift 5.9+ 19 | - Xcode 16.0+ 20 | 21 | ## 安装 22 | 23 | ### Swift Package Manager 24 | 25 | 通过在 `Package.swift` 文件中添加以下内容来将此包添加到您的项目: 26 | 27 | ```swift 28 | dependencies: [ 29 | .package(url: "https://github.com/1998code/ChartJS-for-Swift", from: "1.0.0") 30 | ] 31 | ``` 32 | 33 | 或者直接从 Xcode 添加: 34 | 1. 文件 > Swift Packages > 添加包依赖 35 | 2. 输入仓库 URL:`https://github.com/1998code/ChartJS-for-Swift` 36 | 37 | ## 用法 38 | 39 | ### 基本示例 40 | 41 | ```swift 42 | import SwiftUI 43 | import ChartJS 44 | 45 | struct ContentView: View { 46 | let dataJson = """ 47 | { 48 | "labels": ["一月", "二月", "三月", "四月", "五月", "六月"], 49 | "datasets": [{ 50 | "label": "2025年销售额", 51 | "data": [12, 19, 3, 5, 2, 3], 52 | "backgroundColor": "rgba(75, 192, 192, 0.2)", 53 | "borderColor": "rgba(75, 192, 192, 1)", 54 | "borderWidth": 1 55 | }] 56 | } 57 | """ 58 | 59 | let configJson = """ 60 | { 61 | "type": "bar", 62 | "options": { 63 | "responsive": true, 64 | "plugins": { 65 | "title": { 66 | "display": true, 67 | "text": "月度销售" 68 | } 69 | }, 70 | "scales": { 71 | "y": { 72 | "beginAtZero": true 73 | } 74 | } 75 | } 76 | } 77 | """ 78 | 79 | var body: some View { 80 | VStack { 81 | Charts(dataJson: dataJson, configJson: configJson) 82 | .frame(height: 300) 83 | .padding() 84 | 85 | Text("月度销售图表") 86 | .font(.caption) 87 | } 88 | } 89 | } 90 | ``` 91 | 92 | ### 多种图表类型 93 | 94 | 您可以通过更改配置中的 `type` 属性来创建不同类型的图表: 95 | 96 | ```swift 97 | // 折线图 98 | let lineConfigJson = """ 99 | { 100 | "type": "line", 101 | "options": { 102 | "responsive": true, 103 | "plugins": { 104 | "title": { 105 | "display": true, 106 | "text": "折线图" 107 | } 108 | } 109 | } 110 | } 111 | """ 112 | 113 | // 饼图 114 | let pieConfigJson = """ 115 | { 116 | "type": "pie", 117 | "options": { 118 | "responsive": true, 119 | "plugins": { 120 | "title": { 121 | "display": true, 122 | "text": "饼图" 123 | } 124 | } 125 | } 126 | } 127 | """ 128 | ``` 129 | 130 | ### 响应式布局 131 | 132 | 为了获得最佳的响应式体验,请使用 GeometryReader: 133 | 134 | ```swift 135 | GeometryReader { geometry in 136 | Charts(dataJson: dataJson, configJson: configJson) 137 | .frame(height: geometry.size.width > geometry.size.height ? 138 | geometry.size.height : // 横向 139 | geometry.size.height * 0.3) // 纵向 140 | .padding() 141 | } 142 | ``` 143 | 144 | ### 自定义 JavaScript 145 | 146 | 如果需要添加自定义 JavaScript 初始化,请使用 `scriptSetup` 参数: 147 | 148 | ```swift 149 | let scriptSetup = """ 150 | 157 | """ 158 | 159 | Charts(dataJson: dataJson, configJson: configJson, scriptSetup: scriptSetup) 160 | ``` 161 | 162 | ## 平台支持 163 | 164 | - **iOS**:完全支持 165 | - **macOS**:即将推出 166 | - **tvOS**:(不支持 WebKit) 167 | - **watchOS**:(不支持 WebKit) 168 | 169 | ## 故障排除 170 | 171 | 如果遇到任何问题: 172 | 173 | 1. 确保您的 JSON 是有效的 174 | 2. 检查您的数据结构是否符合 Chart.js 的预期 175 | 3. 对于复杂图表,请先在 Chart.js 网站上测试您的配置 176 | 177 | ## 许可证 178 | 179 | 本项目基于 MIT 许可证可用。有关更多信息,请参阅 LICENSE 文件。 180 | 181 | ## 致谢 182 | 183 | - [Chart.js](https://www.chartjs.org/) - 简单而灵活的 JavaScript 图表库 184 | 185 | --- 186 | 187 | ## 翻译 188 | 189 | 本文档还提供以下语言版本: 190 | 191 | [English](../README.md) | [繁中](README.zh-TW.md) / [简中](README.zh-CN.md) / [粵語](README.zh-HK.md) | [日本語](README.ja.md) | [Español](README.es.md) 192 | 193 | 欢迎通过 pull request 添加新的语言翻译或修正任何错误。 -------------------------------------------------------------------------------- /README/README.zh-HK.md: -------------------------------------------------------------------------------- 1 | # ChartJS for Swift 2 | 3 | Chart.js 嘅 SwiftUI 封裝,畀你喺 iOS ~~同 macOS~~ 應用程式入面輕鬆建立靚靚嘅響應式圖表。 4 | 5 | ![ChartJS@3x](https://github.com/user-attachments/assets/9051153e-7d07-4b4f-8da7-fa9ac1793349) 6 | 7 | ## 功能特點 8 | 9 | - 支援 Chart.js 提供嘅各種圖表類型(條形圖、折線圖、圓餅圖、環形圖等) 10 | - 完全可以自訂嘅 Chart.js 配置選項 11 | - 適用於 iOS 同 macOS(tvOS 同 watchOS 提供回退訊息) 12 | - 同 `Charts` 視圖嘅 SwiftUI 整合 13 | - 響應式設計,可以適應唔同屏幕尺寸 14 | 15 | ## 系統要求 16 | 17 | - iOS 13.0+ / ~~macOS 10.15+~~(即將推出) 18 | - Swift 5.9+ 19 | - Xcode 16.0+ 20 | 21 | ## 安裝 22 | 23 | ### Swift Package Manager 24 | 25 | 喺 `Package.swift` 文件入面加入以下內容,將呢個套件添加到你嘅專案: 26 | 27 | ```swift 28 | dependencies: [ 29 | .package(url: "https://github.com/1998code/ChartJS-for-Swift", from: "1.0.0") 30 | ] 31 | ``` 32 | 33 | 或者直接由 Xcode 添加: 34 | 1. 文件 > Swift Packages > 添加套件依賴 35 | 2. 輸入倉庫 URL:`https://github.com/1998code/ChartJS-for-Swift` 36 | 37 | ## 使用方法 38 | 39 | ### 基本示例 40 | 41 | ```swift 42 | import SwiftUI 43 | import ChartJS 44 | 45 | struct ContentView: View { 46 | let dataJson = """ 47 | { 48 | "labels": ["一月", "二月", "三月", "四月", "五月", "六月"], 49 | "datasets": [{ 50 | "label": "2025年銷售額", 51 | "data": [12, 19, 3, 5, 2, 3], 52 | "backgroundColor": "rgba(75, 192, 192, 0.2)", 53 | "borderColor": "rgba(75, 192, 192, 1)", 54 | "borderWidth": 1 55 | }] 56 | } 57 | """ 58 | 59 | let configJson = """ 60 | { 61 | "type": "bar", 62 | "options": { 63 | "responsive": true, 64 | "plugins": { 65 | "title": { 66 | "display": true, 67 | "text": "月度銷售" 68 | } 69 | }, 70 | "scales": { 71 | "y": { 72 | "beginAtZero": true 73 | } 74 | } 75 | } 76 | } 77 | """ 78 | 79 | var body: some View { 80 | VStack { 81 | Charts(dataJson: dataJson, configJson: configJson) 82 | .frame(height: 300) 83 | .padding() 84 | 85 | Text("月度銷售圖表") 86 | .font(.caption) 87 | } 88 | } 89 | } 90 | ``` 91 | 92 | ### 多種圖表類型 93 | 94 | 你可以通過更改配置入面嘅 `type` 屬性來創建唔同類型嘅圖表: 95 | 96 | ```swift 97 | // 折線圖 98 | let lineConfigJson = """ 99 | { 100 | "type": "line", 101 | "options": { 102 | "responsive": true, 103 | "plugins": { 104 | "title": { 105 | "display": true, 106 | "text": "折線圖" 107 | } 108 | } 109 | } 110 | } 111 | """ 112 | 113 | // 圓餅圖 114 | let pieConfigJson = """ 115 | { 116 | "type": "pie", 117 | "options": { 118 | "responsive": true, 119 | "plugins": { 120 | "title": { 121 | "display": true, 122 | "text": "圓餅圖" 123 | } 124 | } 125 | } 126 | } 127 | """ 128 | ``` 129 | 130 | ### 響應式布局 131 | 132 | 要獲得最佳嘅響應式體驗,請使用 GeometryReader: 133 | 134 | ```swift 135 | GeometryReader { geometry in 136 | Charts(dataJson: dataJson, configJson: configJson) 137 | .frame(height: geometry.size.width > geometry.size.height ? 138 | geometry.size.height : // 橫向 139 | geometry.size.height * 0.3) // 縱向 140 | .padding() 141 | } 142 | ``` 143 | 144 | ### 自定義 JavaScript 145 | 146 | 如果需要添加自定義 JavaScript 初始化,請使用 `scriptSetup` 參數: 147 | 148 | ```swift 149 | let scriptSetup = """ 150 | 157 | """ 158 | 159 | Charts(dataJson: dataJson, configJson: configJson, scriptSetup: scriptSetup) 160 | ``` 161 | 162 | ## 平台支援 163 | 164 | - **iOS**:完全支援 165 | - **macOS**:即將推出 166 | - **tvOS**:(唔支援 WebKit) 167 | - **watchOS**:(唔支援 WebKit) 168 | 169 | ## 疑難排解 170 | 171 | 如果遇到任何問題: 172 | 173 | 1. 確保你嘅 JSON 係有效嘅 174 | 2. 檢查你嘅數據結構係咪符合 Chart.js 嘅預期 175 | 3. 對於複雜圖表,請先喺 Chart.js 網站上測試你嘅配置 176 | 177 | ## 許可證 178 | 179 | 呢個項目係基於 MIT 許可證可用。有關更多資訊,請參閱 LICENSE 文件。 180 | 181 | ## 致謝 182 | 183 | - [Chart.js](https://www.chartjs.org/) - 簡單而靈活嘅 JavaScript 圖表庫 184 | 185 | --- 186 | 187 | ## 翻譯 188 | 189 | 呢個文檔仲有以下語言版本: 190 | 191 | [English](../README.md) | [繁中](README.zh-TW.md) / [简中](README.zh-CN.md) / [粵語](README.zh-HK.md) | [日本語](README.ja.md) | [Español](README.es.md) 192 | 193 | 歡迎透過 pull request 添加新嘅語言翻譯或者修正任何錯誤。 -------------------------------------------------------------------------------- /README/README.zh-TW.md: -------------------------------------------------------------------------------- 1 | # ChartJS for Swift 2 | 3 | Chart.js 的 SwiftUI 封裝,讓您可以在 iOS ~~和 macOS~~ 應用程式中輕鬆建立美觀、響應式的圖表。 4 | 5 | ![ChartJS@3x](https://github.com/user-attachments/assets/9051153e-7d07-4b4f-8da7-fa9ac1793349) 6 | 7 | ## 功能特點 8 | 9 | - 支援 Chart.js 提供的各種圖表類型(條形圖、折線圖、圓餅圖、環形圖等) 10 | - 完全可定制的 Chart.js 配置選項 11 | - 適用於 iOS 和 macOS(tvOS 和 watchOS 提供回退訊息) 12 | - 與 `Charts` 視圖的 SwiftUI 整合 13 | - 響應式設計,可適應不同屏幕尺寸 14 | 15 | ## 系統要求 16 | 17 | - iOS 13.0+ / ~~macOS 10.15+~~(即將推出) 18 | - Swift 5.9+ 19 | - Xcode 16.0+ 20 | 21 | ## 安裝 22 | 23 | ### Swift Package Manager 24 | 25 | 通過在 `Package.swift` 文件中添加以下內容來將此套件添加到您的專案: 26 | 27 | ```swift 28 | dependencies: [ 29 | .package(url: "https://github.com/1998code/ChartJS-for-Swift", from: "1.0.0") 30 | ] 31 | ``` 32 | 33 | 或者直接從 Xcode 添加: 34 | 1. 文件 > Swift Packages > 添加套件依賴 35 | 2. 輸入儲存庫 URL:`https://github.com/1998code/ChartJS-for-Swift` 36 | 37 | ## 使用方法 38 | 39 | ### 基本示例 40 | 41 | ```swift 42 | import SwiftUI 43 | import ChartJS 44 | 45 | struct ContentView: View { 46 | let dataJson = """ 47 | { 48 | "labels": ["一月", "二月", "三月", "四月", "五月", "六月"], 49 | "datasets": [{ 50 | "label": "2025年銷售額", 51 | "data": [12, 19, 3, 5, 2, 3], 52 | "backgroundColor": "rgba(75, 192, 192, 0.2)", 53 | "borderColor": "rgba(75, 192, 192, 1)", 54 | "borderWidth": 1 55 | }] 56 | } 57 | """ 58 | 59 | let configJson = """ 60 | { 61 | "type": "bar", 62 | "options": { 63 | "responsive": true, 64 | "plugins": { 65 | "title": { 66 | "display": true, 67 | "text": "月度銷售" 68 | } 69 | }, 70 | "scales": { 71 | "y": { 72 | "beginAtZero": true 73 | } 74 | } 75 | } 76 | } 77 | """ 78 | 79 | var body: some View { 80 | VStack { 81 | Charts(dataJson: dataJson, configJson: configJson) 82 | .frame(height: 300) 83 | .padding() 84 | 85 | Text("月度銷售圖表") 86 | .font(.caption) 87 | } 88 | } 89 | } 90 | ``` 91 | 92 | ### 多種圖表類型 93 | 94 | 您可以通過更改配置中的 `type` 屬性來創建不同類型的圖表: 95 | 96 | ```swift 97 | // 折線圖 98 | let lineConfigJson = """ 99 | { 100 | "type": "line", 101 | "options": { 102 | "responsive": true, 103 | "plugins": { 104 | "title": { 105 | "display": true, 106 | "text": "折線圖" 107 | } 108 | } 109 | } 110 | } 111 | """ 112 | 113 | // 圓餅圖 114 | let pieConfigJson = """ 115 | { 116 | "type": "pie", 117 | "options": { 118 | "responsive": true, 119 | "plugins": { 120 | "title": { 121 | "display": true, 122 | "text": "圓餅圖" 123 | } 124 | } 125 | } 126 | } 127 | """ 128 | ``` 129 | 130 | ### 響應式布局 131 | 132 | 為了獲得最佳的響應式體驗,請使用 GeometryReader: 133 | 134 | ```swift 135 | GeometryReader { geometry in 136 | Charts(dataJson: dataJson, configJson: configJson) 137 | .frame(height: geometry.size.width > geometry.size.height ? 138 | geometry.size.height : // 橫向 139 | geometry.size.height * 0.3) // 縱向 140 | .padding() 141 | } 142 | ``` 143 | 144 | ### 自定義 JavaScript 145 | 146 | 如果需要添加自定義 JavaScript 初始化,請使用 `scriptSetup` 參數: 147 | 148 | ```swift 149 | let scriptSetup = """ 150 | 157 | """ 158 | 159 | Charts(dataJson: dataJson, configJson: configJson, scriptSetup: scriptSetup) 160 | ``` 161 | 162 | ## 平台支持 163 | 164 | - **iOS**:完全支持 165 | - **macOS**:即將推出 166 | - **tvOS**:(不支持 WebKit) 167 | - **watchOS**:(不支持 WebKit) 168 | 169 | ## 故障排除 170 | 171 | 如果遇到任何問題: 172 | 173 | 1. 確保您的 JSON 是有效的 174 | 2. 檢查您的數據結構是否符合 Chart.js 的預期 175 | 3. 對於複雜圖表,請先在 Chart.js 網站上測試您的配置 176 | 177 | ## 許可證 178 | 179 | 本項目基於 MIT 許可證可用。有關更多信息,請參閱 LICENSE 文件。 180 | 181 | ## 致謝 182 | 183 | - [Chart.js](https://www.chartjs.org/) - 簡單而靈活的 JavaScript 圖表庫 184 | 185 | --- 186 | 187 | ## 翻譯 188 | 189 | 本文檔還提供以下語言版本: 190 | 191 | [English](../README.md) | [繁中](README.zh-TW.md) / [简中](README.zh-CN.md) / [粵語](README.zh-HK.md) | [日本語](README.ja.md) | [Español](README.es.md) 192 | 193 | 歡迎通過 pull request 添加新的語言翻譯或修正任何錯誤。 -------------------------------------------------------------------------------- /Sources/ChartJS/ChartJS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChartJS.swift 3 | // ChartJS 4 | // 5 | // Created by Ming on 21/5/2025. 6 | // 7 | 8 | import SwiftUI 9 | import WebKit 10 | 11 | /// A SwiftUI view that renders charts using Chart.js 12 | public struct Charts: View { 13 | var dataJson: String 14 | var configJson: String 15 | var height: CGFloat? 16 | var scriptSetup: String? 17 | 18 | /// Initialize a new Charts view 19 | /// - Parameters: 20 | /// - dataJson: JSON string containing chart data 21 | /// - configJson: JSON string containing chart configuration 22 | /// - height: Optional height for the chart 23 | /// - scriptSetup: Optional JavaScript for additional chart setup 24 | public init(dataJson: String, configJson: String, height: CGFloat? = nil, scriptSetup: String? = nil) { 25 | self.dataJson = dataJson 26 | self.configJson = configJson 27 | self.height = height 28 | self.scriptSetup = scriptSetup 29 | } 30 | 31 | public var body: some View { 32 | ChartView(dataJson: dataJson, configJson: configJson, height: height, scriptSetup: scriptSetup) 33 | } 34 | } 35 | 36 | /// The underlying UIViewRepresentable that renders Chart.js content 37 | struct ChartView: UIViewRepresentable { 38 | // Support for JSON configuration 39 | var dataJson: String 40 | var configJson: String 41 | 42 | // Optional height parameter 43 | var height: CGFloat? 44 | 45 | // Optional script setup for advanced customization 46 | var scriptSetup: String? 47 | 48 | init(dataJson: String, configJson: String, height: CGFloat? = nil, scriptSetup: String? = nil) { 49 | self.dataJson = dataJson 50 | self.configJson = configJson 51 | self.height = height 52 | self.scriptSetup = scriptSetup 53 | } 54 | 55 | func makeUIView(context: Context) -> WKWebView { 56 | let webView = WKWebView() 57 | webView.navigationDelegate = context.coordinator 58 | 59 | // Configure webView for transparency 60 | webView.isOpaque = false 61 | webView.backgroundColor = UIColor.clear 62 | webView.scrollView.backgroundColor = UIColor.clear 63 | 64 | return webView 65 | } 66 | 67 | func updateUIView(_ webView: WKWebView, context: Context) { 68 | webView.scrollView.isScrollEnabled = true 69 | webView.translatesAutoresizingMaskIntoConstraints = true 70 | let htmlContent = generateChartHTMLWithJSON(dataJson: dataJson, configJson: configJson) 71 | if let chartJsBundleUrl = chartJSBundle.url(forResource: "chart", withExtension: "js") { 72 | let tempDir = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("chartjs-local") 73 | let tempChartJsUrl = tempDir.appendingPathComponent("chart.js") 74 | if !FileManager.default.fileExists(atPath: tempChartJsUrl.path) { 75 | try? FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true) 76 | do { 77 | try FileManager.default.copyItem(at: chartJsBundleUrl, to: tempChartJsUrl) 78 | print("[ChartJS] Copied chart.js to temp: \(tempChartJsUrl.path)") 79 | } catch { 80 | print("[ChartJS] Error copying chart.js: \(error)") 81 | } 82 | } else { 83 | print("[ChartJS] chart.js already exists at temp: \(tempChartJsUrl.path)") 84 | } 85 | print("[ChartJS] baseURL for WKWebView: \(tempDir)") 86 | webView.loadHTMLString(htmlContent, baseURL: tempDir) 87 | context.coordinator.previousDataJson = dataJson 88 | context.coordinator.previousConfigJson = configJson 89 | webView.evaluateJavaScript("console.log('Chart loaded with data');", completionHandler: nil) 90 | } else { 91 | print("[ChartJS] Error: chart.js file not found in the bundle") 92 | webView.loadHTMLString(htmlContent, baseURL: nil) 93 | } 94 | } 95 | 96 | func makeCoordinator() -> Coordinator { 97 | Coordinator(self) 98 | } 99 | 100 | class Coordinator: NSObject, WKNavigationDelegate { 101 | var parent: ChartView 102 | var previousDataJson: String = "" 103 | var previousConfigJson: String = "" 104 | 105 | init(_ parent: ChartView) { 106 | self.parent = parent 107 | self.previousDataJson = parent.dataJson 108 | self.previousConfigJson = parent.configJson 109 | } 110 | } 111 | 112 | private func generateChartHTMLWithJSON(dataJson: String, configJson: String) -> String { 113 | // Always use local chart.js, never CDN 114 | let chartJsScriptTag = "" 115 | return """ 116 | 117 | 118 | 119 | 120 | \(chartJsScriptTag) 121 | 130 | \(scriptSetup ?? "") 131 | 132 | 133 |
134 | 135 |
136 | 164 | 165 | 166 | """ 167 | } 168 | } 169 | 170 | // Extension to get the correct bundle for resources 171 | private extension ChartView { 172 | var chartJSBundle: Bundle { 173 | #if SWIFT_PACKAGE 174 | return Bundle.module 175 | #else 176 | let candidates = [ 177 | Bundle(for: Coordinator.self), 178 | Bundle.main, 179 | ] 180 | for bundle in candidates { 181 | if bundle.path(forResource: "chart", ofType: "js") != nil { 182 | return bundle 183 | } 184 | } 185 | return Bundle.main 186 | #endif 187 | } 188 | } 189 | 190 | #Preview("Simple Chart") { 191 | let dataJson = """ 192 | { 193 | "labels": ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"], 194 | "datasets": [{ 195 | "label": "Data", 196 | "data": [12, 19, 3, 5, 2, 3], 197 | "backgroundColor": "rgba(75, 192, 192, 0.2)", 198 | "borderColor": "rgba(75, 192, 192, 1)", 199 | "borderWidth": 1 200 | }] 201 | } 202 | """ 203 | 204 | let dataJson2 = """ 205 | { 206 | "labels": ["Jan", "Feb", "Mar", "Apr", "May", "Jun"], 207 | "datasets": [ 208 | { 209 | "label": "Sales", 210 | "data": [12, 19, 3, 5, 2, 3], 211 | "backgroundColor": "rgba(255, 99, 132, 0.2)", 212 | "borderColor": "rgba(255, 99, 132, 1)", 213 | "borderWidth": 1 214 | }, 215 | { 216 | "label": "Revenue", 217 | "data": [7, 11, 5, 8, 3, 7], 218 | "backgroundColor": "rgba(54, 162, 235, 0.2)", 219 | "borderColor": "rgba(54, 162, 235, 1)", 220 | "borderWidth": 1 221 | } 222 | ] 223 | } 224 | """ 225 | 226 | let configJson = """ 227 | { 228 | "type": "bar", 229 | "options": { 230 | "responsive": true, 231 | "plugins": { 232 | "title": { 233 | "display": true, 234 | "text": "ChartJS for Swift" 235 | } 236 | }, 237 | "scales": { 238 | "y": { 239 | "beginAtZero": true 240 | } 241 | } 242 | } 243 | } 244 | """ 245 | 246 | return GeometryReader { geometry in 247 | VStack{ 248 | Charts(dataJson: dataJson, configJson: configJson) 249 | .frame(height: geometry.size.width > geometry.size.height ? 250 | geometry.size.height : // landscape 251 | geometry.size.height * 0.3) // portrait 252 | .padding() 253 | .frame(maxWidth: .infinity, maxHeight: .infinity) 254 | 255 | Charts(dataJson: dataJson2, configJson: configJson) 256 | .frame(height: geometry.size.width > geometry.size.height ? 257 | geometry.size.height : // landscape 258 | geometry.size.height * 0.3) // portrait 259 | .padding() 260 | .frame(maxWidth: .infinity, maxHeight: .infinity) 261 | } 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /Tests/ChartJSTests/ChartJSTests.swift: -------------------------------------------------------------------------------- 1 | import Testing 2 | @testable import ChartJS 3 | 4 | @Test func example() async throws { 5 | // Write your test here and use APIs like `#expect(...)` to check expected conditions. 6 | } 7 | --------------------------------------------------------------------------------