@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +266 -267
- package/package.json +2 -2
- package/pipeline/scripts/_notices.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +68 -68
- package/pipeline/skills/shared/README.md +70 -70
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
- package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
- package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
- package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
- package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
- package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
- package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
- package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
- package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
- package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
- package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
- package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
- package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
- package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
- package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
- package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
- package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
- package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
- package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
- package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
- package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
- package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
- package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
- package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
- package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
- package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
- package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
- package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
- package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
- package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
- package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
- package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
- package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
- package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
- package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
- package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
- package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
- package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
- package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
- package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
- package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
- package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
- package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
- package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
- package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
- package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
- package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
- package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
- package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
- package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
- package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
- package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
- package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
- package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
- package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
- package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
- package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
- package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
- package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
- package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
- package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
- package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
- package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
- package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
- package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
- package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
- package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
- package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
- package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
- package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
- package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
- package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
- package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
- package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
- package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
- package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
- package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
- package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
- package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
- package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
- package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
- package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
- package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
- package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
- package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
- package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
- package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
- package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
- package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
- package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
- package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
- package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
- package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
- package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
- package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
- package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
- package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
- package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
- package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
- package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
- package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
- package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
- package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
- package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
- package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
- package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
- package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
- package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
- package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
- package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
- package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
- package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
- package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
- package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
- package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
- package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
- package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
- package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
- package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
- package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
- package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
- package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
- package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
- package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
- package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
- package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
- package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
- package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
- package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
- package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
- package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
- package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
- package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
- package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
- package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
- package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
- package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
- package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
- package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
- package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
- package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
- package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
- package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
- package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
- package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
- package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
- package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
- package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
- package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
- package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
- package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
- package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
- package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
- package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
- package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
- package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
- package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
- package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
- package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
- package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
- package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
- package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
- package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
- package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
- package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
- package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
- package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
- package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
- package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
- package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
- package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
- package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
- package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
- package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
- package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
- package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
- package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
- package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
|
@@ -1,262 +1,161 @@
|
|
|
1
|
-
# Swift Charts Patterns
|
|
1
|
+
# Swift Charts Patterns
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
```swift
|
|
7
|
-
import SwiftUI
|
|
8
|
-
import Charts
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
---
|
|
3
|
+
Companion to the `swift-charts` skill. Any source file touching these types
|
|
4
|
+
imports `Charts` next to `SwiftUI`.
|
|
12
5
|
|
|
13
6
|
## Contents
|
|
14
7
|
|
|
15
|
-
- [Data
|
|
16
|
-
- [
|
|
17
|
-
- [
|
|
18
|
-
- [
|
|
19
|
-
- [
|
|
20
|
-
- [
|
|
21
|
-
- [
|
|
22
|
-
- [Function
|
|
23
|
-
- [3D Charts and Surfaces (
|
|
8
|
+
- [Modelling the Data](#modelling-the-data)
|
|
9
|
+
- [Bars](#bars)
|
|
10
|
+
- [Lines](#lines)
|
|
11
|
+
- [Pies and Donuts](#pies-and-donuts)
|
|
12
|
+
- [Combining Marks](#combining-marks)
|
|
13
|
+
- [A Tooltip on Selection](#a-tooltip-on-selection)
|
|
14
|
+
- [Scrolling with a Bound Position](#scrolling-with-a-bound-position)
|
|
15
|
+
- [Function Plots (iOS 18)](#function-plots-ios-18)
|
|
16
|
+
- [3D Charts and Surfaces (iOS 26)](#3d-charts-and-surfaces-ios-26)
|
|
24
17
|
- [Accessibility](#accessibility)
|
|
25
|
-
- [Dynamic Type and
|
|
26
|
-
- [Performance:
|
|
18
|
+
- [Dynamic Type and Colour](#dynamic-type-and-colour)
|
|
19
|
+
- [Performance: When to Vectorize](#performance-when-to-vectorize)
|
|
27
20
|
- [Dark Mode and Theming](#dark-mode-and-theming)
|
|
28
|
-
- [Heat Map
|
|
29
|
-
- [Stacking
|
|
30
|
-
- [MarkDimension
|
|
31
|
-
- [
|
|
32
|
-
- [ChartProxy and
|
|
33
|
-
- [Quick Reference
|
|
34
|
-
- [Apple Documentation
|
|
21
|
+
- [Heat Map](#heat-map)
|
|
22
|
+
- [Stacking](#stacking)
|
|
23
|
+
- [`MarkDimension`](#markdimension)
|
|
24
|
+
- [Symbols](#symbols)
|
|
25
|
+
- [`ChartProxy` and Coordinates](#chartproxy-and-coordinates)
|
|
26
|
+
- [Modifier Quick Reference](#modifier-quick-reference)
|
|
27
|
+
- [Apple Documentation](#apple-documentation)
|
|
35
28
|
|
|
36
|
-
## Data
|
|
37
|
-
|
|
38
|
-
Use `@Observable` for chart data models. Pair with `@State` in views.
|
|
29
|
+
## Modelling the Data
|
|
39
30
|
|
|
40
31
|
```swift
|
|
32
|
+
import Charts
|
|
33
|
+
import SwiftUI
|
|
34
|
+
|
|
35
|
+
struct MonthlyUsage: Identifiable, Sendable {
|
|
36
|
+
let id = UUID()
|
|
37
|
+
let month: Date
|
|
38
|
+
let kilowattHours: Double
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@MainActor
|
|
41
42
|
@Observable
|
|
42
|
-
class
|
|
43
|
-
var
|
|
43
|
+
final class UsageModel {
|
|
44
|
+
private(set) var months: [MonthlyUsage] = []
|
|
44
45
|
|
|
45
46
|
func load() async {
|
|
46
|
-
|
|
47
|
+
months = await UsageService.shared.lastTwelveMonths()
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
struct
|
|
51
|
-
|
|
52
|
-
let month: Date
|
|
53
|
-
let revenue: Double
|
|
54
|
-
let category: String
|
|
55
|
-
}
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
```swift
|
|
59
|
-
struct SalesDashboard: View {
|
|
60
|
-
@State private var model = SalesModel()
|
|
51
|
+
struct UsageChart: View {
|
|
52
|
+
@State private var model = UsageModel()
|
|
61
53
|
|
|
62
54
|
var body: some View {
|
|
63
|
-
Chart(model.
|
|
64
|
-
BarMark(
|
|
65
|
-
|
|
66
|
-
y: .value("Revenue", item.revenue)
|
|
67
|
-
)
|
|
68
|
-
.foregroundStyle(by: .value("Category", item.category))
|
|
55
|
+
Chart(model.months) { entry in
|
|
56
|
+
BarMark(x: .value("Month", entry.month, unit: .month),
|
|
57
|
+
y: .value("kWh", entry.kilowattHours))
|
|
69
58
|
}
|
|
70
59
|
.task { await model.load() }
|
|
71
60
|
}
|
|
72
61
|
}
|
|
73
62
|
```
|
|
74
63
|
|
|
75
|
-
|
|
64
|
+
The view owns the model with `@State` and starts loading from `.task`.
|
|
76
65
|
|
|
77
|
-
##
|
|
66
|
+
## Bars
|
|
78
67
|
|
|
79
|
-
|
|
68
|
+
Side by side instead of stacked: combine `.position(by:)` with
|
|
69
|
+
`.foregroundStyle(by:)`.
|
|
80
70
|
|
|
81
71
|
```swift
|
|
82
|
-
Chart(
|
|
83
|
-
BarMark(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
)
|
|
72
|
+
Chart(results) { row in
|
|
73
|
+
BarMark(x: .value("Quarter", row.quarter), y: .value("Units", row.units))
|
|
74
|
+
.foregroundStyle(by: .value("Model", row.model))
|
|
75
|
+
.position(by: .value("Model", row.model))
|
|
87
76
|
}
|
|
88
77
|
```
|
|
89
78
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
When multiple bars share the same x value, they stack automatically:
|
|
79
|
+
Horizontal bars with only value labels on the category axis:
|
|
93
80
|
|
|
94
81
|
```swift
|
|
95
|
-
Chart(
|
|
96
|
-
BarMark(
|
|
97
|
-
x: .value("Quarter", item.quarter),
|
|
98
|
-
y: .value("Sales", item.sales)
|
|
99
|
-
)
|
|
100
|
-
.foregroundStyle(by: .value("Product", item.product))
|
|
101
|
-
}
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### Grouped bars
|
|
105
|
-
|
|
106
|
-
Use `.position(by:)` to place bars side by side instead of stacking:
|
|
107
|
-
|
|
108
|
-
```swift
|
|
109
|
-
Chart(data) { item in
|
|
110
|
-
BarMark(
|
|
111
|
-
x: .value("Quarter", item.quarter),
|
|
112
|
-
y: .value("Sales", item.sales)
|
|
113
|
-
)
|
|
114
|
-
.foregroundStyle(by: .value("Product", item.product))
|
|
115
|
-
.position(by: .value("Product", item.product))
|
|
116
|
-
}
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
### Horizontal bars
|
|
120
|
-
|
|
121
|
-
Swap the x and y axes:
|
|
122
|
-
|
|
123
|
-
```swift
|
|
124
|
-
Chart(data) { item in
|
|
125
|
-
BarMark(
|
|
126
|
-
x: .value("Sales", item.sales),
|
|
127
|
-
y: .value("Region", item.region)
|
|
128
|
-
)
|
|
82
|
+
Chart(languages) { item in
|
|
83
|
+
BarMark(x: .value("Speakers", item.speakers), y: .value("Language", item.name))
|
|
129
84
|
}
|
|
130
85
|
.chartYAxis {
|
|
131
|
-
AxisMarks { _ in
|
|
132
|
-
AxisValueLabel()
|
|
133
|
-
}
|
|
86
|
+
AxisMarks { _ in AxisValueLabel() }
|
|
134
87
|
}
|
|
135
88
|
```
|
|
136
89
|
|
|
137
|
-
|
|
90
|
+
Shares of 100 percent per bar: `BarMark(x:y:stacking: .normalized)`.
|
|
138
91
|
|
|
139
|
-
|
|
140
|
-
Chart(data) { item in
|
|
141
|
-
BarMark(
|
|
142
|
-
x: .value("Quarter", item.quarter),
|
|
143
|
-
y: .value("Sales", item.sales),
|
|
144
|
-
stacking: .normalized
|
|
145
|
-
)
|
|
146
|
-
.foregroundStyle(by: .value("Product", item.product))
|
|
147
|
-
}
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
### Bar with annotation
|
|
92
|
+
A value on top of each bar:
|
|
151
93
|
|
|
152
94
|
```swift
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
y: .value("Revenue", item.revenue)
|
|
157
|
-
)
|
|
158
|
-
.annotation(position: .top, alignment: .center, spacing: 4) {
|
|
159
|
-
Text(item.revenue, format: .currency(code: "USD").precision(.fractionLength(0)))
|
|
95
|
+
BarMark(x: .value("Store", row.store), y: .value("Sales", row.sales))
|
|
96
|
+
.annotation(position: .top) {
|
|
97
|
+
Text(row.sales, format: .currency(code: "EUR").precision(.fractionLength(0)))
|
|
160
98
|
.font(.caption2)
|
|
161
99
|
}
|
|
162
|
-
}
|
|
163
100
|
```
|
|
164
101
|
|
|
165
|
-
|
|
102
|
+
A Gantt chart colours interval bars by status:
|
|
166
103
|
|
|
167
104
|
```swift
|
|
168
105
|
Chart(tasks) { task in
|
|
169
|
-
BarMark(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
)
|
|
174
|
-
.foregroundStyle(by: .value("Status", task.status))
|
|
106
|
+
BarMark(xStart: .value("Start", task.start),
|
|
107
|
+
xEnd: .value("End", task.end),
|
|
108
|
+
y: .value("Task", task.title))
|
|
109
|
+
.foregroundStyle(by: .value("Phase", task.phase.rawValue))
|
|
175
110
|
}
|
|
176
111
|
```
|
|
177
112
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
## Line Chart Patterns
|
|
113
|
+
## Lines
|
|
181
114
|
|
|
182
|
-
|
|
115
|
+
Points on the line: put a `LineMark` and a `PointMark` in the same closure.
|
|
183
116
|
|
|
184
117
|
```swift
|
|
185
|
-
Chart(
|
|
186
|
-
LineMark(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
x: .value("Date", item.date),
|
|
192
|
-
y: .value("Price", item.price)
|
|
193
|
-
)
|
|
194
|
-
.symbolSize(30)
|
|
118
|
+
Chart(temperatures) { sample in
|
|
119
|
+
LineMark(x: .value("Hour", sample.hour), y: .value("Celsius", sample.value))
|
|
120
|
+
.foregroundStyle(by: .value("City", sample.city))
|
|
121
|
+
.symbol(by: .value("City", sample.city))
|
|
122
|
+
PointMark(x: .value("Hour", sample.hour), y: .value("Celsius", sample.value))
|
|
123
|
+
.symbolSize(30)
|
|
195
124
|
}
|
|
196
125
|
```
|
|
197
126
|
|
|
198
|
-
|
|
127
|
+
Pairing `.foregroundStyle(by:)` with `.symbol(by:)` keeps series apart for
|
|
128
|
+
people who cannot tell the colours apart.
|
|
199
129
|
|
|
200
|
-
|
|
201
|
-
Chart(temperatures) { item in
|
|
202
|
-
LineMark(
|
|
203
|
-
x: .value("Date", item.date),
|
|
204
|
-
y: .value("Temp", item.temperature)
|
|
205
|
-
)
|
|
206
|
-
.foregroundStyle(by: .value("City", item.city))
|
|
207
|
-
.symbol(by: .value("City", item.city))
|
|
208
|
-
}
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
### Line with area fill
|
|
130
|
+
A line over a fading area:
|
|
212
131
|
|
|
213
132
|
```swift
|
|
214
|
-
Chart(
|
|
215
|
-
AreaMark(
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
.
|
|
220
|
-
.
|
|
221
|
-
colors: [.blue.opacity(0.3), .blue.opacity(0.05)],
|
|
222
|
-
startPoint: .top,
|
|
223
|
-
endPoint: .bottom
|
|
224
|
-
)
|
|
225
|
-
)
|
|
226
|
-
LineMark(
|
|
227
|
-
x: .value("Date", item.date),
|
|
228
|
-
y: .value("Value", item.value)
|
|
229
|
-
)
|
|
230
|
-
.foregroundStyle(.blue)
|
|
133
|
+
Chart(prices) { point in
|
|
134
|
+
AreaMark(x: .value("Day", point.day), y: .value("Price", point.close))
|
|
135
|
+
.foregroundStyle(.linearGradient(
|
|
136
|
+
colors: [.indigo.opacity(0.35), .indigo.opacity(0.02)],
|
|
137
|
+
startPoint: .top, endPoint: .bottom))
|
|
138
|
+
LineMark(x: .value("Day", point.day), y: .value("Price", point.close))
|
|
139
|
+
.foregroundStyle(.indigo)
|
|
231
140
|
}
|
|
232
141
|
```
|
|
233
142
|
|
|
234
|
-
|
|
143
|
+
Interpolation methods:
|
|
235
144
|
|
|
236
|
-
| Method |
|
|
237
|
-
|
|
238
|
-
| `.linear` |
|
|
239
|
-
| `.monotone` | Smooth
|
|
240
|
-
| `.catmullRom` | Smooth general
|
|
241
|
-
| `.cardinal` | Smooth with adjustable tension |
|
|
242
|
-
| `.stepStart` |
|
|
243
|
-
| `.stepCenter` | Step function centered on data point |
|
|
244
|
-
| `.stepEnd` | Step function ending at data point |
|
|
145
|
+
| Method | Result |
|
|
146
|
+
|--------|--------|
|
|
147
|
+
| `.linear` | Straight segments (the default) |
|
|
148
|
+
| `.monotone` | Smooth without overshooting; keeps monotonic data monotonic |
|
|
149
|
+
| `.catmullRom` | Smooth curve for general data |
|
|
150
|
+
| `.cardinal` | Smooth curve with adjustable tension |
|
|
151
|
+
| `.stepStart`, `.stepCenter`, `.stepEnd` | Steps placed at the start, middle or end of each interval |
|
|
245
152
|
|
|
246
|
-
|
|
247
|
-
LineMark(x: .value("X", item.x), y: .value("Y", item.y))
|
|
248
|
-
.interpolationMethod(.monotone)
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
### Sparkline (minimal inline chart)
|
|
153
|
+
A sparkline:
|
|
252
154
|
|
|
253
155
|
```swift
|
|
254
|
-
Chart(
|
|
255
|
-
LineMark(
|
|
256
|
-
|
|
257
|
-
y: .value("Value", item.value)
|
|
258
|
-
)
|
|
259
|
-
.interpolationMethod(.catmullRom)
|
|
156
|
+
Chart(recent) { point in
|
|
157
|
+
LineMark(x: .value("t", point.index), y: .value("v", point.value))
|
|
158
|
+
.interpolationMethod(.catmullRom)
|
|
260
159
|
}
|
|
261
160
|
.chartXAxis(.hidden)
|
|
262
161
|
.chartYAxis(.hidden)
|
|
@@ -264,625 +163,360 @@ Chart(recentData) { item in
|
|
|
264
163
|
.frame(width: 80, height: 30)
|
|
265
164
|
```
|
|
266
165
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
## Pie and Donut Chart Patterns (SectorMark, iOS 17+)
|
|
270
|
-
|
|
271
|
-
Use strictly positive values for sectors. Filter, aggregate, or show zero and
|
|
272
|
-
negative values outside the pie or donut so angular sizes remain meaningful.
|
|
273
|
-
|
|
274
|
-
### Basic pie chart
|
|
275
|
-
|
|
276
|
-
```swift
|
|
277
|
-
Chart(products, id: \.name) { item in
|
|
278
|
-
SectorMark(angle: .value("Sales", item.sales))
|
|
279
|
-
.foregroundStyle(by: .value("Product", item.name))
|
|
280
|
-
}
|
|
281
|
-
```
|
|
166
|
+
## Pies and Donuts
|
|
282
167
|
|
|
283
|
-
|
|
168
|
+
An inner radius of 0.618 (the golden ratio) gives a balanced donut. Put a label
|
|
169
|
+
in the hole with `.chartBackground`:
|
|
284
170
|
|
|
285
171
|
```swift
|
|
286
|
-
Chart(
|
|
287
|
-
SectorMark(
|
|
288
|
-
|
|
289
|
-
innerRadius: .ratio(0.618),
|
|
290
|
-
outerRadius: .inset(10),
|
|
291
|
-
angularInset: 1
|
|
292
|
-
)
|
|
293
|
-
.cornerRadius(4)
|
|
294
|
-
.foregroundStyle(by: .value("Product", item.name))
|
|
295
|
-
}
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
### Donut chart with center label
|
|
299
|
-
|
|
300
|
-
```swift
|
|
301
|
-
Chart(products, id: \.name) { item in
|
|
302
|
-
SectorMark(
|
|
303
|
-
angle: .value("Sales", item.sales),
|
|
304
|
-
innerRadius: .ratio(0.618),
|
|
305
|
-
angularInset: 1
|
|
306
|
-
)
|
|
307
|
-
.cornerRadius(4)
|
|
308
|
-
.foregroundStyle(by: .value("Product", item.name))
|
|
172
|
+
Chart(portfolio) { holding in
|
|
173
|
+
SectorMark(angle: .value("Value", holding.value), innerRadius: .ratio(0.618))
|
|
174
|
+
.foregroundStyle(by: .value("Asset", holding.asset))
|
|
309
175
|
}
|
|
310
176
|
.chartBackground { _ in
|
|
311
177
|
VStack {
|
|
312
|
-
Text("Total")
|
|
313
|
-
|
|
314
|
-
.foregroundStyle(.secondary)
|
|
315
|
-
Text("\(totalSales, format: .number)")
|
|
316
|
-
.font(.title2.bold())
|
|
178
|
+
Text("Total").font(.caption)
|
|
179
|
+
Text(portfolioTotal, format: .currency(code: "EUR")).font(.headline)
|
|
317
180
|
}
|
|
318
181
|
}
|
|
319
182
|
```
|
|
320
183
|
|
|
321
|
-
|
|
184
|
+
Selecting a slice. The binding holds a position along the summed values, so
|
|
185
|
+
map it back to a slice:
|
|
322
186
|
|
|
323
187
|
```swift
|
|
324
|
-
struct
|
|
325
|
-
let
|
|
326
|
-
|
|
327
|
-
let sales: Double
|
|
328
|
-
}
|
|
188
|
+
struct PortfolioDonut: View {
|
|
189
|
+
let holdings: [Holding]
|
|
190
|
+
@State private var selectedAngle: Double?
|
|
329
191
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
var
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
return range.contains(selectedAngle)
|
|
192
|
+
private var selectedHolding: Holding? {
|
|
193
|
+
guard let angle = selectedAngle else { return nil }
|
|
194
|
+
var start = 0.0
|
|
195
|
+
for holding in holdings {
|
|
196
|
+
let end = start + holding.value
|
|
197
|
+
if (start..<end).contains(angle) { return holding }
|
|
198
|
+
start = end
|
|
199
|
+
}
|
|
200
|
+
return nil
|
|
340
201
|
}
|
|
341
|
-
}
|
|
342
202
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
.opacity(selectedProduct == nil || selectedProduct?.name == item.name ? 1.0 : 0.4)
|
|
203
|
+
var body: some View {
|
|
204
|
+
Chart(holdings) { holding in
|
|
205
|
+
SectorMark(angle: .value("Value", holding.value), innerRadius: .ratio(0.6))
|
|
206
|
+
.foregroundStyle(by: .value("Asset", holding.asset))
|
|
207
|
+
.opacity(selectedHolding == nil || selectedHolding?.id == holding.id ? 1 : 0.4)
|
|
208
|
+
}
|
|
209
|
+
.chartAngleSelection(value: $selectedAngle)
|
|
210
|
+
}
|
|
352
211
|
}
|
|
353
|
-
.chartAngleSelection(value: $selectedAngle)
|
|
354
212
|
```
|
|
355
213
|
|
|
356
|
-
|
|
357
|
-
sector label. Convert that value through cumulative sector ranges before using
|
|
358
|
-
it to highlight or annotate a category.
|
|
359
|
-
|
|
360
|
-
### Grouping small slices
|
|
361
|
-
|
|
362
|
-
Limit pie/donut charts to 5-7 positive-value sectors. Group the rest into "Other":
|
|
214
|
+
Keep five to seven positive slices:
|
|
363
215
|
|
|
364
216
|
```swift
|
|
365
|
-
func
|
|
366
|
-
let
|
|
367
|
-
let
|
|
368
|
-
let
|
|
369
|
-
guard
|
|
370
|
-
return
|
|
217
|
+
func topSlices(_ items: [Holding], keep limit: Int = 5) -> [Holding] {
|
|
218
|
+
let ordered = items.filter { $0.value > 0 }.sorted { $0.value > $1.value }
|
|
219
|
+
let head = Array(ordered.prefix(limit))
|
|
220
|
+
let remainder = ordered.dropFirst(limit).reduce(0) { $0 + $1.value }
|
|
221
|
+
guard remainder > 0 else { return head }
|
|
222
|
+
return head + [Holding(asset: "Other", value: remainder)]
|
|
371
223
|
}
|
|
372
224
|
```
|
|
373
225
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
## Combined Chart Patterns
|
|
226
|
+
## Combining Marks
|
|
377
227
|
|
|
378
|
-
|
|
228
|
+
A range band with an average line:
|
|
379
229
|
|
|
380
230
|
```swift
|
|
381
|
-
Chart(
|
|
382
|
-
AreaMark(
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
)
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
LineMark(
|
|
390
|
-
x: .value("Date", item.date),
|
|
391
|
-
y: .value("Average", item.average)
|
|
392
|
-
)
|
|
393
|
-
.foregroundStyle(.blue)
|
|
394
|
-
.lineStyle(StrokeStyle(lineWidth: 2))
|
|
231
|
+
Chart(week) { day in
|
|
232
|
+
AreaMark(x: .value("Day", day.date),
|
|
233
|
+
yStart: .value("Low", day.low),
|
|
234
|
+
yEnd: .value("High", day.high))
|
|
235
|
+
.opacity(0.15)
|
|
236
|
+
LineMark(x: .value("Day", day.date), y: .value("Mean", day.mean))
|
|
237
|
+
.lineStyle(StrokeStyle(lineWidth: 2))
|
|
395
238
|
}
|
|
396
239
|
```
|
|
397
240
|
|
|
398
|
-
|
|
241
|
+
Bars with a target line:
|
|
399
242
|
|
|
400
243
|
```swift
|
|
401
244
|
Chart {
|
|
402
|
-
ForEach(
|
|
403
|
-
BarMark(
|
|
404
|
-
x: .value("Month", item.month),
|
|
405
|
-
y: .value("Revenue", item.revenue)
|
|
406
|
-
)
|
|
245
|
+
ForEach(weeks) { week in
|
|
246
|
+
BarMark(x: .value("Week", week.label), y: .value("Hours", week.hours))
|
|
407
247
|
}
|
|
408
|
-
RuleMark(y: .value("Target",
|
|
409
|
-
.
|
|
410
|
-
.
|
|
411
|
-
.annotation(position: .top, alignment: .leading) {
|
|
412
|
-
Text("Target: \(targetRevenue, format: .number)")
|
|
413
|
-
.font(.caption)
|
|
414
|
-
.foregroundStyle(.red)
|
|
415
|
-
}
|
|
248
|
+
RuleMark(y: .value("Target", 38))
|
|
249
|
+
.lineStyle(StrokeStyle(lineWidth: 1, dash: [4, 4]))
|
|
250
|
+
.annotation(position: .top, alignment: .leading) { Text("38 h") }
|
|
416
251
|
}
|
|
417
252
|
```
|
|
418
253
|
|
|
419
|
-
|
|
254
|
+
Observations with a fitted curve; marks and plots can share a chart:
|
|
420
255
|
|
|
421
256
|
```swift
|
|
422
257
|
Chart {
|
|
423
|
-
ForEach(
|
|
424
|
-
PointMark(
|
|
425
|
-
x: .value("Experience", item.yearsExperience),
|
|
426
|
-
y: .value("Salary", item.salary)
|
|
427
|
-
)
|
|
428
|
-
.opacity(0.6)
|
|
258
|
+
ForEach(samples) { sample in
|
|
259
|
+
PointMark(x: .value("Dose", sample.dose), y: .value("Response", sample.response))
|
|
429
260
|
}
|
|
430
|
-
LinePlot(x: "
|
|
431
|
-
|
|
261
|
+
LinePlot(x: "Dose", y: "Response", domain: 0...10) { dose in
|
|
262
|
+
fit.slope * dose + fit.intercept
|
|
432
263
|
}
|
|
433
264
|
.foregroundStyle(.red)
|
|
434
|
-
.lineStyle(StrokeStyle(lineWidth: 1
|
|
265
|
+
.lineStyle(StrokeStyle(lineWidth: 1, dash: [3, 3]))
|
|
435
266
|
}
|
|
436
267
|
```
|
|
437
268
|
|
|
438
|
-
|
|
269
|
+
## A Tooltip on Selection
|
|
439
270
|
|
|
440
|
-
|
|
271
|
+
```swift
|
|
272
|
+
struct ActivityChart: View {
|
|
273
|
+
let days: [ActivityDay]
|
|
274
|
+
@State private var focusedDate: Date?
|
|
441
275
|
|
|
442
|
-
|
|
276
|
+
private var focusedDay: ActivityDay? {
|
|
277
|
+
guard let focusedDate else { return nil }
|
|
278
|
+
let calendar = Calendar.current
|
|
279
|
+
return days.first { calendar.isDate($0.date, inSameDayAs: focusedDate) }
|
|
280
|
+
}
|
|
443
281
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
.symbolSize(60)
|
|
462
|
-
.annotation(position: .top) {
|
|
463
|
-
Text("\(match.value, format: .number)")
|
|
464
|
-
.font(.caption)
|
|
465
|
-
.padding(4)
|
|
466
|
-
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 4))
|
|
282
|
+
var body: some View {
|
|
283
|
+
Chart {
|
|
284
|
+
ForEach(days) { day in
|
|
285
|
+
LineMark(x: .value("Date", day.date, unit: .day),
|
|
286
|
+
y: .value("Minutes", day.minutes))
|
|
287
|
+
}
|
|
288
|
+
if let focusedDay {
|
|
289
|
+
RuleMark(x: .value("Date", focusedDay.date, unit: .day))
|
|
290
|
+
.foregroundStyle(.secondary)
|
|
291
|
+
PointMark(x: .value("Date", focusedDay.date, unit: .day),
|
|
292
|
+
y: .value("Minutes", focusedDay.minutes))
|
|
293
|
+
.symbolSize(80)
|
|
294
|
+
.annotation(position: .top) {
|
|
295
|
+
Text("\(focusedDay.minutes) min")
|
|
296
|
+
.padding(6)
|
|
297
|
+
.background(.regularMaterial, in: .rect(cornerRadius: 6))
|
|
298
|
+
}
|
|
467
299
|
}
|
|
468
300
|
}
|
|
301
|
+
.chartXSelection(value: $focusedDate)
|
|
469
302
|
}
|
|
470
|
-
.chartXSelection(value: $selectedDate)
|
|
471
303
|
}
|
|
472
304
|
```
|
|
473
305
|
|
|
474
|
-
|
|
306
|
+
## Scrolling with a Bound Position
|
|
475
307
|
|
|
476
|
-
|
|
308
|
+
`chartScrollPosition(x:)` takes a binding to a non-optional `Plottable` value,
|
|
309
|
+
so bind a `Date`, not a `Date?`:
|
|
477
310
|
|
|
478
311
|
```swift
|
|
479
|
-
@State private var
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
.
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
.valueAligned(matching: DateComponents(hour: 0), majorAlignment: .page)
|
|
493
|
-
)
|
|
494
|
-
.chartXAxis {
|
|
495
|
-
AxisMarks(values: .stride(by: .day)) { value in
|
|
496
|
-
AxisGridLine()
|
|
497
|
-
AxisValueLabel(format: .dateTime.weekday(.abbreviated))
|
|
498
|
-
}
|
|
312
|
+
@State private var windowStart = Date.now.addingTimeInterval(-7 * 86_400)
|
|
313
|
+
|
|
314
|
+
Chart(readings) { reading in
|
|
315
|
+
BarMark(x: .value("Day", reading.date, unit: .day),
|
|
316
|
+
y: .value("Litres", reading.litres))
|
|
317
|
+
}
|
|
318
|
+
.chartScrollableAxes(.horizontal)
|
|
319
|
+
.chartXVisibleDomain(length: 3600 * 24 * 7)
|
|
320
|
+
.chartScrollPosition(x: $windowStart)
|
|
321
|
+
.chartXAxis {
|
|
322
|
+
AxisMarks(values: .stride(by: .day)) { value in
|
|
323
|
+
AxisGridLine()
|
|
324
|
+
AxisValueLabel(format: .dateTime.weekday(.abbreviated))
|
|
499
325
|
}
|
|
500
326
|
}
|
|
501
327
|
```
|
|
502
328
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
## Function Plotting (LinePlot, iOS 18+)
|
|
506
|
-
|
|
507
|
-
### Standard function y = f(x)
|
|
329
|
+
## Function Plots (iOS 18)
|
|
508
330
|
|
|
509
331
|
```swift
|
|
510
332
|
Chart {
|
|
511
|
-
LinePlot(x: "
|
|
512
|
-
sin(x)
|
|
513
|
-
}
|
|
514
|
-
.foregroundStyle(.blue)
|
|
333
|
+
LinePlot(x: "Angle", y: "Amplitude", domain: -2 * .pi ... 2 * .pi) { cos($0) }
|
|
515
334
|
}
|
|
516
|
-
.chartYScale(domain: -1.
|
|
517
|
-
```
|
|
335
|
+
.chartYScale(domain: -1.2...1.2)
|
|
518
336
|
|
|
519
|
-
### Parametric function (x, y) = f(t)
|
|
520
|
-
|
|
521
|
-
```swift
|
|
522
337
|
Chart {
|
|
523
|
-
LinePlot(x: "
|
|
524
|
-
(x: cos(
|
|
338
|
+
LinePlot(x: "Across", y: "Up", t: "Turn", domain: 0...(2 * .pi)) { turn in
|
|
339
|
+
(x: 2 * cos(turn), y: 2 * sin(turn))
|
|
525
340
|
}
|
|
526
341
|
}
|
|
527
|
-
.chartXScale(domain: -
|
|
528
|
-
.chartYScale(domain: -
|
|
529
|
-
```
|
|
342
|
+
.chartXScale(domain: -2.5...2.5)
|
|
343
|
+
.chartYScale(domain: -2.5...2.5)
|
|
530
344
|
|
|
531
|
-
### Range area function
|
|
532
|
-
|
|
533
|
-
```swift
|
|
534
345
|
Chart {
|
|
535
|
-
AreaPlot(x: "x", yStart: "
|
|
536
|
-
(yStart:
|
|
346
|
+
AreaPlot(x: "x", yStart: "lower", yEnd: "upper", domain: 0...4) { x in
|
|
347
|
+
(yStart: x - 0.5, yEnd: x * x / 2 + 0.5)
|
|
537
348
|
}
|
|
538
|
-
.foregroundStyle(.blue.opacity(0.2))
|
|
539
349
|
}
|
|
540
350
|
```
|
|
541
351
|
|
|
542
|
-
|
|
352
|
+
The last chart fills the band between two functions of x.
|
|
543
353
|
|
|
544
|
-
## 3D Charts and Surfaces (
|
|
354
|
+
## 3D Charts and Surfaces (iOS 26)
|
|
545
355
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
compare, and make accessible.
|
|
550
|
-
|
|
551
|
-
### SurfacePlot for bivariate functions
|
|
356
|
+
`Chart3D` is for data with a real third dimension: points in (x, y, z), 3D
|
|
357
|
+
regions, or a surface of two variables. Categories, time series and shares stay
|
|
358
|
+
in 2D: labels, comparisons and VoiceOver all work better there.
|
|
552
359
|
|
|
553
360
|
```swift
|
|
554
|
-
|
|
361
|
+
struct SensorCloud: View {
|
|
362
|
+
let readings: [SensorReading]
|
|
555
363
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
364
|
+
var body: some View {
|
|
365
|
+
Chart3D(readings) { reading in
|
|
366
|
+
PointMark(x: .value("X", reading.x),
|
|
367
|
+
y: .value("Y", reading.y),
|
|
368
|
+
z: .value("Z", reading.z))
|
|
369
|
+
.foregroundStyle(by: .value("Sensor", reading.sensor))
|
|
370
|
+
}
|
|
371
|
+
.chart3DCameraProjection(.perspective)
|
|
559
372
|
}
|
|
560
|
-
.foregroundStyle(.heightBased)
|
|
561
|
-
}
|
|
562
|
-
.chartXScale(domain: -2...2)
|
|
563
|
-
.chartYScale(domain: -1...1)
|
|
564
|
-
.chartZScale(domain: -2...2)
|
|
565
|
-
.chart3DPose($pose)
|
|
566
|
-
```
|
|
567
|
-
|
|
568
|
-
### 3D point cloud
|
|
569
|
-
|
|
570
|
-
```swift
|
|
571
|
-
Chart3D(points) { point in
|
|
572
|
-
PointMark(
|
|
573
|
-
x: .value("Width", point.x),
|
|
574
|
-
y: .value("Height", point.y),
|
|
575
|
-
z: .value("Depth", point.z)
|
|
576
|
-
)
|
|
577
|
-
.foregroundStyle(by: .value("Cluster", point.cluster))
|
|
578
373
|
}
|
|
579
|
-
.chart3DCameraProjection(.perspective)
|
|
580
374
|
```
|
|
581
375
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
-
|
|
585
|
-
-
|
|
586
|
-
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
---
|
|
376
|
+
Review notes:
|
|
377
|
+
- The z axis must mean something and carry a label.
|
|
378
|
+
- Set explicit x, y and z domains so charts can be compared.
|
|
379
|
+
- Bind a `Chart3DPose` when users should rotate the chart.
|
|
380
|
+
- `SurfacePlot` draws y as a function of x and z; the 3D mark initialisers
|
|
381
|
+
draw observed points and regions.
|
|
590
382
|
|
|
591
383
|
## Accessibility
|
|
592
384
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
### Best practices
|
|
627
|
-
|
|
628
|
-
- Use descriptive strings in `.value("Label", ...)` -- these become VoiceOver labels.
|
|
629
|
-
- Add `.accessibilityLabel` and `.accessibilityValue` for context beyond raw numbers.
|
|
630
|
-
- Test with VoiceOver enabled: navigate the chart and verify each element is announced.
|
|
631
|
-
- Avoid `.accessibilityHidden(true)` on data-bearing marks.
|
|
632
|
-
|
|
633
|
-
---
|
|
634
|
-
|
|
635
|
-
## Dynamic Type and Color Considerations
|
|
636
|
-
|
|
637
|
-
### Dynamic Type
|
|
638
|
-
|
|
639
|
-
Charts automatically adjust axis label sizes with Dynamic Type. Avoid fixed
|
|
640
|
-
frame heights that clip labels at larger text sizes.
|
|
641
|
-
|
|
642
|
-
```swift
|
|
643
|
-
// WRONG -- clips at large text sizes
|
|
644
|
-
Chart(data) { ... }
|
|
645
|
-
.frame(height: 200)
|
|
646
|
-
|
|
647
|
-
// CORRECT -- adaptive height
|
|
648
|
-
Chart(data) { ... }
|
|
649
|
-
.frame(minHeight: 200)
|
|
650
|
-
.frame(maxHeight: 400)
|
|
651
|
-
```
|
|
652
|
-
|
|
653
|
-
Test charts at the "Accessibility Extra Extra Extra Large" text size to verify
|
|
654
|
-
axis labels, annotations, and legends remain readable.
|
|
655
|
-
|
|
656
|
-
### Color
|
|
657
|
-
|
|
658
|
-
- Avoid encoding meaning solely in color. Pair `.foregroundStyle(by:)` with
|
|
659
|
-
`.symbol(by:)` or `.lineStyle(by:)` for distinguishability.
|
|
660
|
-
- Use system colors that adapt to both light and dark modes.
|
|
661
|
-
- Test with color blindness simulations in the Accessibility Inspector.
|
|
662
|
-
|
|
663
|
-
```swift
|
|
664
|
-
LineMark(x: .value("Date", item.date), y: .value("Value", item.value))
|
|
665
|
-
.foregroundStyle(by: .value("Category", item.category))
|
|
666
|
-
.symbol(by: .value("Category", item.category))
|
|
667
|
-
.lineStyle(by: .value("Category", item.category))
|
|
668
|
-
```
|
|
669
|
-
|
|
670
|
-
---
|
|
671
|
-
|
|
672
|
-
## Performance: Vectorized Plots for Large Datasets
|
|
673
|
-
|
|
674
|
-
For datasets exceeding 1000 data points, use vectorized plot types instead of
|
|
675
|
-
individual marks. Vectorized plots accept entire collections and render
|
|
676
|
-
efficiently.
|
|
677
|
-
|
|
678
|
-
### When to use vectorized plots
|
|
679
|
-
|
|
680
|
-
| Data Points | Recommended Approach |
|
|
681
|
-
|---|---|
|
|
682
|
-
| < 100 | Individual marks (`BarMark`, `LineMark`, etc.) |
|
|
683
|
-
| 100 - 1000 | Either approach; profile if performance matters |
|
|
684
|
-
| > 1000 | Vectorized plots (`BarPlot`, `LinePlot`, etc.) |
|
|
685
|
-
|
|
686
|
-
### Data-driven vectorized plot
|
|
687
|
-
|
|
688
|
-
```swift
|
|
689
|
-
struct SensorReading: Identifiable {
|
|
385
|
+
- Swift Charts builds VoiceOver descriptions from axis labels and values, so
|
|
386
|
+
the strings in `.value("Label", ...)` are what VoiceOver says. Make them
|
|
387
|
+
descriptive.
|
|
388
|
+
- Add `.accessibilityLabel` and `.accessibilityValue` to marks when the
|
|
389
|
+
default is not clear enough.
|
|
390
|
+
- Vectorized plots take key paths:
|
|
391
|
+
`.accessibilityLabel(\.spokenName)` and `.accessibilityValue(\.spokenValue)`.
|
|
392
|
+
- The system creates an audio graph for VoiceOver users automatically; clear,
|
|
393
|
+
consistent labels make it useful.
|
|
394
|
+
- Move through every element with VoiceOver. Never mark data-bearing marks
|
|
395
|
+
`.accessibilityHidden(true)`.
|
|
396
|
+
|
|
397
|
+
## Dynamic Type and Colour
|
|
398
|
+
|
|
399
|
+
- Axis labels follow Dynamic Type. Avoid fixed heights; give a range such as
|
|
400
|
+
`.frame(minHeight: 200)` with `.frame(maxHeight: 400)`.
|
|
401
|
+
- Check axes, annotations and legends at the largest accessibility size
|
|
402
|
+
(Accessibility Extra Extra Extra Large).
|
|
403
|
+
- Colour must not be the only carrier of meaning. Pair
|
|
404
|
+
`.foregroundStyle(by:)` with `.symbol(by:)`, `.lineStyle(by:)` or both.
|
|
405
|
+
- Prefer system colours, which adapt, and check colour-blindness filters in
|
|
406
|
+
Accessibility Inspector.
|
|
407
|
+
|
|
408
|
+
## Performance: When to Vectorize
|
|
409
|
+
|
|
410
|
+
| Points | Use |
|
|
411
|
+
|--------|-----|
|
|
412
|
+
| Fewer than 100 | Individual marks |
|
|
413
|
+
| 100 to 1,000 | Either; profile if it feels slow |
|
|
414
|
+
| More than 1,000 | Vectorized plots |
|
|
415
|
+
|
|
416
|
+
```swift
|
|
417
|
+
struct TemperatureSample: Identifiable {
|
|
690
418
|
let id: Int
|
|
691
|
-
let
|
|
692
|
-
let
|
|
693
|
-
var
|
|
694
|
-
var
|
|
695
|
-
Text("\(timestamp.formatted(.dateTime.hour().minute())): \(temperature, specifier: "%.1f") degrees")
|
|
696
|
-
}
|
|
419
|
+
let time: Date
|
|
420
|
+
let celsius: Double
|
|
421
|
+
var tint: Color { celsius > 30 ? .red : .blue }
|
|
422
|
+
var spokenValue: Text { Text("\(celsius, specifier: "%.1f") degrees") }
|
|
697
423
|
}
|
|
698
424
|
|
|
699
425
|
Chart {
|
|
700
|
-
LinePlot(
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
y: .value("Temperature", \.temperature)
|
|
704
|
-
)
|
|
705
|
-
.foregroundStyle(.blue)
|
|
426
|
+
LinePlot(samples, x: .value("Time", \.time), y: .value("Celsius", \.celsius))
|
|
427
|
+
.foregroundStyle(\.tint)
|
|
428
|
+
.accessibilityValue(\.spokenValue)
|
|
706
429
|
}
|
|
707
430
|
```
|
|
708
431
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
// CORRECT
|
|
720
|
-
BarPlot(data, x: .value("X", \.x), y: .value("Y", \.y))
|
|
721
|
-
.foregroundStyle(\.color) // KeyPath first
|
|
722
|
-
.opacity(0.8) // value modifier second
|
|
723
|
-
```
|
|
724
|
-
|
|
725
|
-
### Available vectorized plot types
|
|
726
|
-
|
|
727
|
-
| Plot Type | Mark Equivalent | Available From |
|
|
728
|
-
|---|---|---|
|
|
729
|
-
| `BarPlot` | `BarMark` | iOS 18+ |
|
|
730
|
-
| `LinePlot` | `LineMark` | iOS 18+ |
|
|
731
|
-
| `PointPlot` | `PointMark` | iOS 18+ |
|
|
732
|
-
| `AreaPlot` | `AreaMark` | iOS 18+ |
|
|
733
|
-
| `RulePlot` | `RuleMark` | iOS 18+ |
|
|
734
|
-
| `RectanglePlot` | `RectangleMark` | iOS 18+ |
|
|
735
|
-
| `SectorPlot` | `SectorMark` | iOS 18+ |
|
|
736
|
-
|
|
737
|
-
---
|
|
432
|
+
| Vectorized (iOS 18) | Mark equivalent |
|
|
433
|
+
|---------------------|-----------------|
|
|
434
|
+
| `BarPlot` | `BarMark` |
|
|
435
|
+
| `LinePlot` | `LineMark` |
|
|
436
|
+
| `PointPlot` | `PointMark` |
|
|
437
|
+
| `AreaPlot` | `AreaMark` |
|
|
438
|
+
| `RulePlot` | `RuleMark` |
|
|
439
|
+
| `RectanglePlot` | `RectangleMark` |
|
|
440
|
+
| `SectorPlot` | `SectorMark` |
|
|
738
441
|
|
|
739
442
|
## Dark Mode and Theming
|
|
740
443
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
Swift Charts inherits the current color scheme automatically. System colors
|
|
744
|
-
(`.blue`, `.orange`, `.green`) adapt to light and dark modes without extra code.
|
|
745
|
-
|
|
746
|
-
### Custom color palettes
|
|
747
|
-
|
|
748
|
-
Use `.chartForegroundStyleScale` to define a consistent palette:
|
|
444
|
+
Charts follow the colour scheme, and system colours adapt on their own.
|
|
749
445
|
|
|
750
446
|
```swift
|
|
751
|
-
Chart(
|
|
752
|
-
BarMark(
|
|
753
|
-
|
|
754
|
-
y: .value("Value", item.value)
|
|
755
|
-
)
|
|
756
|
-
.foregroundStyle(by: .value("Category", item.category))
|
|
447
|
+
Chart(spending) { entry in
|
|
448
|
+
BarMark(x: .value("Period", entry.period), y: .value("Spent", entry.amount))
|
|
449
|
+
.foregroundStyle(by: .value("Kind", entry.kind))
|
|
757
450
|
}
|
|
758
451
|
.chartForegroundStyleScale([
|
|
759
|
-
"
|
|
760
|
-
"
|
|
761
|
-
"
|
|
762
|
-
"Books": .green,
|
|
763
|
-
"Other": .gray
|
|
452
|
+
"Food": Color.orange,
|
|
453
|
+
"Travel": Color.teal,
|
|
454
|
+
"Home": Color.purple,
|
|
764
455
|
])
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
```swift
|
|
770
|
-
Chart(data) { ... }
|
|
771
|
-
.chartPlotStyle { plotArea in
|
|
772
|
-
plotArea
|
|
773
|
-
.background(.quaternary.opacity(0.3))
|
|
774
|
-
.border(.quaternary, width: 0.5)
|
|
456
|
+
.chartPlotStyle { plot in
|
|
457
|
+
plot.background(.quaternary.opacity(0.3))
|
|
458
|
+
.border(.separator, width: 0.5)
|
|
775
459
|
}
|
|
776
|
-
```
|
|
777
|
-
|
|
778
|
-
### Axis styling
|
|
779
|
-
|
|
780
|
-
```swift
|
|
781
460
|
.chartXAxisStyle { axis in
|
|
782
|
-
axis.background(.
|
|
783
|
-
}
|
|
784
|
-
```
|
|
785
|
-
|
|
786
|
-
### Testing dark mode
|
|
787
|
-
|
|
788
|
-
Always preview charts in both light and dark color schemes. In Xcode previews:
|
|
789
|
-
|
|
790
|
-
```swift
|
|
791
|
-
#Preview {
|
|
792
|
-
ChartView()
|
|
793
|
-
.preferredColorScheme(.dark)
|
|
461
|
+
axis.background(.thinMaterial)
|
|
794
462
|
}
|
|
795
463
|
```
|
|
796
464
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
---
|
|
465
|
+
`chartForegroundStyleScale` fixes one colour per category across the app.
|
|
466
|
+
Preview both schemes (`.preferredColorScheme(.dark)` in a `#Preview`) and check
|
|
467
|
+
that labels and grid lines stay readable, data keeps its contrast, and
|
|
468
|
+
annotations and legends adapt.
|
|
803
469
|
|
|
804
|
-
## Heat Map
|
|
470
|
+
## Heat Map
|
|
805
471
|
|
|
806
472
|
```swift
|
|
807
|
-
Chart(
|
|
808
|
-
RectangleMark(
|
|
809
|
-
|
|
810
|
-
y: .value("Day", item.day)
|
|
811
|
-
)
|
|
812
|
-
.foregroundStyle(by: .value("Count", item.count))
|
|
473
|
+
Chart(bookings) { cell in
|
|
474
|
+
RectangleMark(x: .value("Hour", cell.hour), y: .value("Weekday", cell.weekday))
|
|
475
|
+
.foregroundStyle(by: .value("Bookings", cell.count))
|
|
813
476
|
}
|
|
814
|
-
.chartForegroundStyleScale(range: Gradient(colors: [.
|
|
815
|
-
```
|
|
816
|
-
|
|
817
|
-
---
|
|
818
|
-
|
|
819
|
-
## Stacking Methods
|
|
820
|
-
|
|
821
|
-
| Method | Behavior |
|
|
822
|
-
|---|---|
|
|
823
|
-
| `.standard` | Default. Regions stack on top showing absolute values. |
|
|
824
|
-
| `.normalized` | Scales to 0-100% proportional view. |
|
|
825
|
-
| `.center` | Baseline centered (streamgraph). |
|
|
826
|
-
| `.unstacked` | Overlapping; no stacking. |
|
|
827
|
-
|
|
828
|
-
```swift
|
|
829
|
-
AreaMark(
|
|
830
|
-
x: .value("Date", item.date),
|
|
831
|
-
y: .value("Revenue", item.revenue),
|
|
832
|
-
stacking: .normalized
|
|
833
|
-
)
|
|
834
|
-
.foregroundStyle(by: .value("Category", item.category))
|
|
477
|
+
.chartForegroundStyleScale(range: Gradient(colors: [.mint, .yellow, .orange]))
|
|
835
478
|
```
|
|
836
479
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
## MarkDimension Options
|
|
480
|
+
## Stacking
|
|
840
481
|
|
|
841
|
-
|
|
842
|
-
|---|---|
|
|
843
|
-
| `.automatic` | Framework decides |
|
|
844
|
-
| `.fixed(CGFloat)` | Exact pixel size |
|
|
845
|
-
| `.inset(CGFloat)` | Inset from available space |
|
|
846
|
-
| `.ratio(CGFloat)` | Proportion of available space (0...1) |
|
|
482
|
+
Pass `stacking:` to `BarMark` or `AreaMark`:
|
|
847
483
|
|
|
848
|
-
|
|
484
|
+
| Value | Effect |
|
|
485
|
+
|-------|--------|
|
|
486
|
+
| `.standard` | Absolute values stacked on each other (default) |
|
|
487
|
+
| `.normalized` | Each stack fills 0 to 100 percent |
|
|
488
|
+
| `.center` | Stacks centred on a baseline, as in a streamgraph |
|
|
489
|
+
| `.unstacked` | Series overlap instead of stacking |
|
|
849
490
|
|
|
850
|
-
|
|
491
|
+
## `MarkDimension`
|
|
851
492
|
|
|
852
|
-
|
|
493
|
+
`.automatic`, `.fixed(CGFloat)`, `.inset(CGFloat)` and `.ratio(CGFloat)` with a
|
|
494
|
+
ratio between 0 and 1. Used for `width` and `height` on `BarMark` and for
|
|
495
|
+
`innerRadius` and `outerRadius` on `SectorMark`.
|
|
853
496
|
|
|
854
|
-
|
|
497
|
+
## Symbols
|
|
855
498
|
|
|
856
|
-
`
|
|
499
|
+
The built-in shapes are `asterisk`, `circle`, `cross`, `diamond`, `pentagon`,
|
|
500
|
+
`plus`, `square` and `triangle`.
|
|
857
501
|
|
|
858
502
|
```swift
|
|
859
|
-
PointMark(x: .value("
|
|
503
|
+
PointMark(x: .value("Speed", run.speed), y: .value("Heart rate", run.bpm))
|
|
860
504
|
.symbol(.diamond)
|
|
861
505
|
.symbolSize(80)
|
|
862
|
-
```
|
|
863
506
|
|
|
864
|
-
|
|
507
|
+
PointMark(x: .value("Speed", run.speed), y: .value("Heart rate", run.bpm))
|
|
508
|
+
.symbol(by: .value("Surface", run.surface))
|
|
865
509
|
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
.symbol(by: .value("Category", item.category))
|
|
510
|
+
PointMark(x: .value("Speed", run.speed), y: .value("Heart rate", run.bpm))
|
|
511
|
+
.symbol { Image(systemName: "figure.run").font(.caption2) }
|
|
869
512
|
```
|
|
870
513
|
|
|
871
|
-
|
|
514
|
+
## `ChartProxy` and Coordinates
|
|
872
515
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
.font(.caption2)
|
|
878
|
-
}
|
|
879
|
-
```
|
|
880
|
-
|
|
881
|
-
---
|
|
882
|
-
|
|
883
|
-
## ChartProxy and Coordinate Conversion
|
|
884
|
-
|
|
885
|
-
Use `chartOverlay` or `chartBackground` to access `ChartProxy`:
|
|
516
|
+
`chartOverlay` and `chartBackground` pass a `ChartProxy` that converts between
|
|
517
|
+
screen positions and data values. Since iOS 17, `plotFrame` (an optional
|
|
518
|
+
anchor) and `plotSize` replace the deprecated `plotAreaFrame` and
|
|
519
|
+
`plotAreaSize`.
|
|
886
520
|
|
|
887
521
|
```swift
|
|
888
522
|
.chartOverlay { proxy in
|
|
@@ -891,86 +525,41 @@ Use `chartOverlay` or `chartBackground` to access `ChartProxy`:
|
|
|
891
525
|
.fill(.clear)
|
|
892
526
|
.contentShape(Rectangle())
|
|
893
527
|
.gesture(
|
|
894
|
-
DragGesture()
|
|
895
|
-
.
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
)
|
|
901
|
-
if let date: Date = proxy.value(atX: location.x) {
|
|
902
|
-
selectedDate = date
|
|
903
|
-
}
|
|
904
|
-
}
|
|
528
|
+
DragGesture(minimumDistance: 0).onChanged { drag in
|
|
529
|
+
guard let plotFrame = proxy.plotFrame else { return }
|
|
530
|
+
let origin = geometry[plotFrame].origin
|
|
531
|
+
let x = drag.location.x - origin.x
|
|
532
|
+
scrubbedDate = proxy.value(atX: x, as: Date.self)
|
|
533
|
+
}
|
|
905
534
|
)
|
|
906
535
|
}
|
|
907
536
|
}
|
|
908
537
|
```
|
|
909
538
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
|
916
|
-
|
|
917
|
-
| `
|
|
918
|
-
| `
|
|
919
|
-
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
-
|
|
928
|
-
-
|
|
929
|
-
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
-
|
|
933
|
-
-
|
|
934
|
-
-
|
|
935
|
-
-
|
|
936
|
-
|
|
937
|
-
### 3D charts (iOS 26+)
|
|
938
|
-
- `Chart3D` with `SurfacePlot` or 3D mark initializers
|
|
939
|
-
- `chart3DPose(_:)` for interactive pose binding
|
|
940
|
-
- `chart3DCameraProjection(_:)` for orthographic/perspective projection
|
|
941
|
-
|
|
942
|
-
### Legend
|
|
943
|
-
- `chartLegend(_:)` -- visibility
|
|
944
|
-
- `chartLegend(position:alignment:spacing:)` -- positioning
|
|
945
|
-
- `chartLegend(position:alignment:spacing:content:)` -- custom content
|
|
946
|
-
|
|
947
|
-
### Selection (iOS 17+)
|
|
948
|
-
- `chartXSelection(value:)` / `chartXSelection(range:)`
|
|
949
|
-
- `chartYSelection(value:)` / `chartYSelection(range:)`
|
|
950
|
-
- `chartAngleSelection(value:)` -- for `SectorMark`
|
|
951
|
-
|
|
952
|
-
### Scrolling (iOS 17+)
|
|
953
|
-
- `chartScrollableAxes(_:)`
|
|
954
|
-
- `chartXVisibleDomain(length:)` / `chartYVisibleDomain(length:)`
|
|
955
|
-
- `chartScrollPosition(initialX:)` / `chartScrollPosition(x:)`
|
|
956
|
-
- `chartScrollTargetBehavior(_:)`
|
|
957
|
-
|
|
958
|
-
### Overlay and Background
|
|
959
|
-
- `chartOverlay(alignment:content:)` -- with `ChartProxy`
|
|
960
|
-
- `chartBackground(alignment:content:)` -- with `ChartProxy`
|
|
961
|
-
- `chartPlotStyle(content:)` -- plot area styling
|
|
962
|
-
|
|
963
|
-
---
|
|
964
|
-
|
|
965
|
-
## Apple Documentation Links
|
|
966
|
-
|
|
967
|
-
- [Swift Charts](https://sosumi.ai/documentation/charts)
|
|
968
|
-
- [Creating a chart using Swift Charts](https://sosumi.ai/documentation/charts/Creating-a-chart-using-Swift-Charts)
|
|
969
|
-
- [BarMark](https://sosumi.ai/documentation/charts/BarMark)
|
|
970
|
-
- [LineMark](https://sosumi.ai/documentation/charts/LineMark)
|
|
971
|
-
- [SectorMark](https://sosumi.ai/documentation/charts/SectorMark)
|
|
972
|
-
- [Chart3D](https://sosumi.ai/documentation/charts/Chart3D)
|
|
973
|
-
- [SurfacePlot](https://sosumi.ai/documentation/charts/SurfacePlot)
|
|
974
|
-
- [LinePlot](https://sosumi.ai/documentation/charts/LinePlot)
|
|
975
|
-
- [AxisMarks](https://sosumi.ai/documentation/charts/AxisMarks)
|
|
976
|
-
- [Swift Charts updates](https://sosumi.ai/documentation/updates/swiftcharts)
|
|
539
|
+
Proxy methods: `position(forX:)`, `position(forY:)`, `value(atX:as:)`,
|
|
540
|
+
`value(atY:as:)`, plus `plotSize` and `plotFrame`.
|
|
541
|
+
|
|
542
|
+
## Modifier Quick Reference
|
|
543
|
+
|
|
544
|
+
| Area | Modifiers |
|
|
545
|
+
|------|-----------|
|
|
546
|
+
| Axes | hide or replace: `chartYAxis(_:)`, `chartXAxis(_:)`, and the builder forms `chartYAxis(content:)`, `chartXAxis(content:)`; titles: `chartYAxisLabel`, `chartXAxisLabel`; axis area styling: `chartYAxisStyle(content:)`, `chartXAxisStyle(content:)` |
|
|
547
|
+
| Scales | `chartYScale(domain:range:type:)`, `chartXScale(domain:range:type:)`; for `Chart3D` also `chartZScale(domain:range:type:)`; categorical colours with `chartForegroundStyleScale(_:)` |
|
|
548
|
+
| 3D | `chart3DCameraProjection(_:)` (perspective or orthographic), `chart3DPose(_:)` |
|
|
549
|
+
| Legend | `chartLegend(_:)` for visibility; `chartLegend(position:alignment:spacing:)` for placement; add `content:` for a custom legend view |
|
|
550
|
+
| Selection (iOS 17) | a single value: `chartXSelection(value:)`, `chartYSelection(value:)`; a span: `chartXSelection(range:)`, `chartYSelection(range:)`; pie slices: `chartAngleSelection(value:)` |
|
|
551
|
+
| Scrolling (iOS 17) | turn on with `chartScrollableAxes(_:)`; window size with `chartXVisibleDomain(length:)` or `chartYVisibleDomain(length:)`; position with `chartScrollPosition(initialX:)` or a bound `chartScrollPosition(x:)`; snapping with `chartScrollTargetBehavior(_:)` |
|
|
552
|
+
| Plot area and layers | `chartPlotStyle(content:)`, `chartBackground(alignment:content:)`, `chartOverlay(alignment:content:)` |
|
|
553
|
+
|
|
554
|
+
## Apple Documentation
|
|
555
|
+
|
|
556
|
+
- [Swift Charts](https://developer.apple.com/documentation/charts)
|
|
557
|
+
- [Building a first chart](https://developer.apple.com/documentation/charts/creating-a-chart-using-swift-charts)
|
|
558
|
+
- [BarMark](https://developer.apple.com/documentation/charts/barmark)
|
|
559
|
+
- [LineMark](https://developer.apple.com/documentation/charts/linemark)
|
|
560
|
+
- [SectorMark](https://developer.apple.com/documentation/charts/sectormark)
|
|
561
|
+
- [Chart3D](https://developer.apple.com/documentation/charts/chart3d)
|
|
562
|
+
- [SurfacePlot](https://developer.apple.com/documentation/charts/surfaceplot)
|
|
563
|
+
- [LinePlot](https://developer.apple.com/documentation/charts/lineplot)
|
|
564
|
+
- [AxisMarks](https://developer.apple.com/documentation/charts/axismarks)
|
|
565
|
+
- [Swift Charts updates](https://developer.apple.com/documentation/updates/swiftcharts)
|