Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Reviews, improves, and writes SwiftUI code following state management, view composition, performance, and iOS 26+ Liquid Glass best practices.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/charts.md
1# SwiftUI Charts Reference23## Table of Contents45- [Overview](#overview)6- [Availability](#availability)7- [Core APIs](#core-apis)8- [Chart Types](#chart-types)9- [Axis Tweaks](#axis-tweaks)10- [Selection APIs](#selection-apis)11- [Annotations](#annotations)12- [ChartProxy and Custom Touch Handling](#chartproxy-and-custom-touch-handling)13- [Modifier Scope](#modifier-scope)14- [Styling and Visual Channels](#styling-and-visual-channels)15- [Composing Multiple Marks](#composing-multiple-marks)16- [Animating Chart Data](#animating-chart-data)17- [Best Practices](#best-practices)1819## Overview2021Swift Charts is Apple's native charting framework for SwiftUI. Use `Chart` with one or more marks to build bar, line, area, point, rule, rectangle, and sector charts. This reference covers the standard 2D chart APIs, axis customization, built-in selection APIs, annotations, and custom touch handling.2223## Availability2425Base `Chart`, custom axes, scales, and most marks require iOS 16 or later.2627- `BarMark`, `LineMark`, `AreaMark`, `PointMark`, `RectangleMark`, and `RuleMark` are available on iOS 16+28- `SectorMark`, built-in selection, and scrollable chart axes require iOS 17+29- Data-driven plot types such as `BarPlot` and `LinePlot` require iOS 18+30- Chart3D and Z-axis APIs exist on iOS 26+; this reference is primarily about 2D `Chart`, with a dedicated Chart3D section below3132```swift33if #available(iOS 17, *) {34// Selection, SectorMark, scrollable axes35} else {36// Base Chart, axes, scales, and core marks37}38```3940## Core APIs4142### Import the Framework4344Always check that the file imports `Charts` before using `Chart`, `Chart3D`, `BarMark`, `SectorMark`, or `ChartProxy`.4546```swift47import SwiftUI48import Charts49```5051If chart types are unresolved, the first thing to verify is that `Charts` is imported in that file.5253### Chart Container5455`Chart` is the root view. Add one or more marks inside it.5657```swift58Chart(sales) { item in59BarMark(60x: .value("Month", item.month),61y: .value("Revenue", item.revenue)62)63}64```6566### Data Models Should Be Identifiable6768Prefer `Identifiable` models for chart data so identity stays stable as data changes.6970```swift71struct SalesPoint: Identifiable {72let id: UUID73let month: String74let revenue: Double75}76```7778If your model cannot conform to `Identifiable`, provide an explicit id key path:7980```swift81Chart(sales, id: \.month) { item in82BarMark(83x: .value("Month", item.month),84y: .value("Revenue", item.revenue)85)86}87```8889### Plottable Values9091Use `.value(_, _)` to describe what each axis value means. Those labels are reused by axes, legends, and accessibility.9293```swift94LineMark(95x: .value("Day", entry.date),96y: .value("Steps", entry.count)97)98```99100## Chart Types101102### BarMark103104```swift105BarMark(106x: .value("Product", product.name),107y: .value("Units", product.units)108)109```110111Stacking via `MarkStackingMethod`: `.standard`, `.normalized`, `.center`, `.unstacked`.112113### LineMark114115```swift116LineMark(117x: .value("Day", day.date),118y: .value("Steps", day.count)119)120.interpolationMethod(.monotone)121```122123Interpolation methods: `.linear`, `.monotone`, `.cardinal`, `.catmullRom`, `.stepStart`, `.stepCenter`, `.stepEnd`. Cardinal and Catmull-Rom accept optional tension/alpha parameters.124125### AreaMark126127```swift128AreaMark(129x: .value("Hour", sample.hour),130y: .value("Temperature", sample.value),131stacking: .unstacked132)133```134135Ranged areas use `yStart`/`yEnd` for bands like min/max or confidence intervals:136137```swift138AreaMark(139x: .value("Day", sample.day),140yStart: .value("Low", sample.low),141yEnd: .value("High", sample.high)142)143```144145### PointMark146147```swift148PointMark(149x: .value("Time", measurement.time),150y: .value("Value", measurement.value)151)152```153154### RectangleMark155156```swift157RectangleMark(158xStart: .value("Start Day", cell.startDay),159xEnd: .value("End Day", cell.endDay),160yStart: .value("Low", cell.low),161yEnd: .value("High", cell.high)162)163```164165### RuleMark166167```swift168RuleMark(y: .value("Goal", 10_000))169.foregroundStyle(.red)170```171172### SectorMark173174Use `SectorMark` for pie and donut-style charts. `SectorMark` requires iOS 17 or later.175176```swift177Chart(expenses) { expense in178SectorMark(179angle: .value("Amount", expense.amount),180innerRadius: .ratio(0.6),181angularInset: 2182)183.foregroundStyle(by: .value("Category", expense.category))184}185```186187Use `innerRadius` to turn a pie chart into a donut chart, and `angularInset` to separate slices visually.188189### Plot Types (iOS 18+)190191iOS 18 adds data-driven plot wrappers: `AreaPlot`, `BarPlot`, `LinePlot`, `PointPlot`, `RectanglePlot`, `RulePlot`, and `SectorPlot`.192193`LinePlot` and `AreaPlot` also accept function closures for plotting mathematical functions without discrete data:194195```swift196if #available(iOS 18, *) {197Chart {198LinePlot(x: "x", y: "sin(x)") { x in199sin(x)200}201}202.chartXScale(domain: -Double.pi ... Double.pi)203.chartYScale(domain: -1.5 ... 1.5)204}205```206207Use plot types when you want a data-first API surface or need function plotting. The underlying chart families stay the same.208209### Chart3D (iOS 26+)210211`Chart3D` is a separate API for 3D chart content. It supports 3D `PointMark`, `RectangleMark`, `RuleMark`, and `SurfacePlot`.212213```swift214if #available(iOS 26, *) {215Chart3D(points) { point in216PointMark(217x: .value("X", point.x),218y: .value("Y", point.y),219z: .value("Z", point.z)220)221}222.chart3DPose(.front)223.chart3DCameraProjection(.perspective)224}225```226227`SurfacePlot` visualizes mathematical surfaces by evaluating a two-variable function:228229```swift230if #available(iOS 26, *) {231Chart3D {232SurfacePlot(x: "x", y: "height", z: "z") { x, z in233sin(x) * cos(z)234}235}236.chartXScale(domain: -Double.pi ... Double.pi)237.chartZScale(domain: -Double.pi ... Double.pi)238}239```240241Camera and pose configuration:242243- **Projection**: `.chart3DCameraProjection(.orthographic)` (default, precise measurements) or `.perspective` (depth effect)244- **Pose presets**: `.chart3DPose(.default)`, `.front`, `.back`, `.left`, `.right`245- **Custom pose**: `.chart3DPose(azimuth: .degrees(45), inclination: .degrees(30))`246- On visionOS, Chart3D supports natural 3D interaction gestures for rotation and exploration247248**Always** gate `Chart3D` with `#available(iOS 26, *)` — it is not available on earlier OS versions.249250## Axis Tweaks251252### Axis Visibility and Labels253254Use `chartXAxis`, `chartYAxis`, `chartXAxisLabel`, and `chartYAxisLabel` on the `Chart` container.255Axis visibility supports `.automatic`, `.visible`, and `.hidden`.256257```swift258Chart(data) { item in259BarMark(260x: .value("Month", item.month),261y: .value("Revenue", item.revenue)262)263}264.chartXAxis(.visible)265.chartYAxis(.hidden)266.chartXAxisLabel("Month")267.chartYAxisLabel("Revenue")268```269270### Custom Axis Marks271272Use `AxisMarks` to control tick placement, labels, and grid lines.273274```swift275Chart(steps) { day in276LineMark(277x: .value("Day", day.date),278y: .value("Steps", day.count)279)280}281.chartXAxis {282AxisMarks(283preset: .aligned,284position: .bottom,285values: .stride(by: .day)286) {287AxisGridLine()288AxisTick(length: .label)289AxisValueLabel(format: .dateTime.weekday(.abbreviated))290}291}292```293294Useful `AxisMarks` inputs:295296- `preset`: `.automatic`, `.extended`, `.aligned`, `.inset`297- `position`: `.automatic`, `.leading`, `.trailing`, `.top`, `.bottom`298- `values`: `.automatic`, `.automatic(desiredCount:)`, `.stride(by:)`, `.stride(by:count:)`, or an explicit array299300### Axis Components301302Within `AxisMarks`, combine the built-in axis components as needed:303304```swift305AxisGridLine()306AxisTick()307AxisValueLabel()308```309310`AxisValueLabel` can be tuned for dense axes:311312```swift313AxisValueLabel(314collisionResolution: .greedy(minimumSpacing: 8),315orientation: .vertical316)317```318319Label orientations: `.automatic`, `.horizontal`, `.vertical`, `.verticalReversed`.320321Collision strategies: `.automatic`, `.greedy`, `.greedy(priority:minimumSpacing:)`, `.truncate`, `.disabled`.322323### Axis Domains and Plot Area Tweaks324325Use scales when you need explicit axis domains or plot area control.326327```swift328Chart(data) { item in329LineMark(330x: .value("Index", item.index),331y: .value("Score", item.score)332)333}334.chartXScale(domain: 0...30)335.chartYScale(domain: 0...100)336.chartPlotStyle { plotArea in337plotArea338.background(.gray.opacity(0.08))339}340```341342You can set one axis domain without forcing the other:343344```swift345.chartXScale(domain: startDate...endDate)346```347348### Scrollable Axes (iOS 17+)349350For larger datasets, make the plot area scroll and control the visible domain.351352```swift353@State private var scrollX = 7354355Chart(data) { item in356BarMark(357x: .value("Day", item.day),358y: .value("Value", item.value)359)360}361.chartScrollableAxes(.horizontal)362.chartXVisibleDomain(length: 7)363.chartScrollPosition(x: $scrollX)364```365366## Selection APIs367368### Single-Value Selection369370Use `chartXSelection(value:)` or `chartYSelection(value:)` for one selected value.371372```swift373@State private var selectedDate: Date?374375Chart(steps) { day in376LineMark(x: .value("Day", day.date), y: .value("Steps", day.count))377378if let selectedDate {379RuleMark(x: .value("Selected Day", selectedDate))380.foregroundStyle(.secondary)381}382}383.chartXSelection(value: $selectedDate)384```385386### Range Selection387388Use `chartXSelection(range:)` or `chartYSelection(range:)` for a dragged range. Bind to a `ClosedRange` whose bound type matches the plotted axis value.389390```swift391@State private var selectedWeeks: ClosedRange<Int>?392393Chart(weeks) { week in394BarMark(x: .value("Week", week.index), y: .value("Revenue", week.revenue))395}396.chartXSelection(range: $selectedWeeks)397```398399### Choosing Single vs Range400401- Use `value:` bindings when only one point or axis value should be selected.402- Use `range:` bindings when users should brush a span (for zoom windows, comparisons, or grouped summaries).403404### Angle Selection405406Use `chartAngleSelection(value:)` with `SectorMark` charts. No built-in range overload for angle selection.407408```swift409@State private var selectedAmount: Double?410411Chart(expenses) { expense in412SectorMark(angle: .value("Amount", expense.amount))413.foregroundStyle(by: .value("Category", expense.category))414}415.chartAngleSelection(value: $selectedAmount)416```417418**Important**: Selection bindings return the plottable axis value, not the full data element. Map back to your model if you need the selected record.419420## Annotations421422Use `annotation(position:)` on a mark when you need labels, callouts, or highlighted values attached to the plotted content.423424```swift425BarMark(426x: .value("Month", item.month),427y: .value("Revenue", item.revenue)428)429.annotation(position: .top) {430Text(item.revenue.formatted())431}432```433434This is useful for selected values, thresholds, summaries, and direct labeling. Common positions include `.overlay`, `.top`, `.bottom`, `.leading`, and `.trailing`.435436## ChartProxy and Custom Touch Handling437438Use `chartOverlay`/`chartBackground` (iOS 16+) or `chartGesture` (iOS 17+) with `ChartProxy` when built-in selection modifiers are not enough.439440```swift441.chartOverlay { proxy in442GeometryReader { geometry in443Rectangle().fill(.clear).contentShape(Rectangle())444.gesture(445DragGesture(minimumDistance: 0)446.onChanged { value in447guard let plotFrame = proxy.plotFrame else { return } // iOS 16: use proxy.plotAreaFrame448let frame = geometry[plotFrame]449let x = value.location.x - frame.origin.x450guard x >= 0, x <= frame.size.width else { return }451selectedDate = proxy.value(atX: x, as: Date.self)452}453.onEnded { _ in selectedDate = nil }454)455}456}457```458459Use `proxy.plotFrame` (iOS 17+) or `proxy.plotAreaFrame` (iOS 16) to get the plot area anchor.460461`ChartProxy` gives you lower-level access to:462463- `value(atX:as:)`, `value(atY:as:)`, and `value(at:as:)` for converting gesture coordinates into chart values464- `position(forX:)`, `position(forY:)`, and `position(for:)` for placing custom overlays or indicators465- `selectXValue(at:)`, `selectYValue(at:)`, `selectXRange(from:to:)`, and `selectYRange(from:to:)` for driving built-in selection from custom gestures466- `plotFrame` (iOS 17+) or `plotAreaFrame` (iOS 16) with `plotSize` for converting between gesture coordinates and the plot area467468`select*` ChartProxy selection methods and `chartGesture` are available on iOS 17+.469470## Modifier Scope471472Apply chart-wide modifiers to the `Chart` container and mark-specific modifiers to the individual mark.473474```swift475Chart(data) { item in476LineMark(477x: .value("Day", item.date),478y: .value("Value", item.value)479)480.interpolationMethod(.monotone) // Mark-level modifier481}482.chartXAxis { AxisMarks() } // Chart-level modifier483.chartYScale(domain: 0...100) // Chart-level modifier484.chartPlotStyle { $0.background(.thinMaterial) }485```486487## Styling and Visual Channels488489### Categorical Coloring490491Use `foregroundStyle(by: .value(...))` to color marks by a data property. Swift Charts generates a legend automatically.492493```swift494Chart(sales) { item in495BarMark(496x: .value("Month", item.month),497y: .value("Revenue", item.revenue)498)499.foregroundStyle(by: .value("Region", item.region))500}501```502503**Avoid** applying `.foregroundStyle(.red)` per mark for categorical data — this suppresses the automatic legend and breaks accessibility.504505### Custom Color Scales506507Use `chartForegroundStyleScale` to control the mapping from data values to colors.508509```swift510.chartForegroundStyleScale([511"North": .blue,512"South": .orange,513"East": .green514])515```516517For dynamic data where not all series appear at every point, use the mapping overload:518519```swift520.chartForegroundStyleScale(domain: regions, mapping: { region in521colorForRegion(region)522})523```524525### Symbol and Size Channels526527Use `symbol(by:)` and `symbolSize(by:)` to encode additional data dimensions on `PointMark` and `LineMark`.528529```swift530Chart(measurements) { item in531PointMark(532x: .value("Time", item.time),533y: .value("Value", item.value)534)535.foregroundStyle(by: .value("Category", item.category))536.symbol(by: .value("Category", item.category))537.symbolSize(by: .value("Weight", item.weight))538}539```540541### Legend Control542543```swift544.chartLegend(.visible)545.chartLegend(.hidden)546.chartLegend(position: .bottom, alignment: .center)547```548549## Composing Multiple Marks550551Combine different mark types inside the same `Chart` closure:552553```swift554// Line with points555LineMark(x: .value("Day", day.date), y: .value("Steps", day.count))556.interpolationMethod(.monotone)557PointMark(x: .value("Day", day.date), y: .value("Steps", day.count))558559// Bars with threshold line560BarMark(x: .value("Month", item.month), y: .value("Revenue", item.revenue))561RuleMark(y: .value("Target", 10_000))562.foregroundStyle(.red)563.lineStyle(StrokeStyle(dash: [5, 3]))564```565566## Animating Chart Data567568Chart marks animate automatically when data identity is stable and changes are wrapped in an animation.569570```swift571withAnimation(.easeInOut) {572chartData = updatedData573}574```575576**Always** use `Identifiable` models (or explicit `id:`) so Swift Charts can match old and new data points and animate transitions between them.577578## Best Practices579580### Do581582- Use semantic `.value(_, _)` labels so axes and accessibility read clearly583- Prefer `Identifiable` models (or explicit `id:`) for stable chart data identity584- Use `foregroundStyle(by:)` for categorical series to get automatic legends and accessibility585- Use `RuleMark` for goals, thresholds, and selected-value indicators586- Use explicit `AxisMarks(values:)` when automatic tick generation gets crowded587- Use `chartXScale` and `chartYScale` when you need stable visual comparisons588- Use `chartXSelection(range:)` or `chartYSelection(range:)` for brushed selection589- Gate iOS 17+ APIs such as `SectorMark` and selection with `#available`590591### Don't592593- Put chart-wide modifiers such as `chartXAxis` or `chartXSelection` on individual marks594- Apply manual `.foregroundStyle(.color)` per mark for categorical data — use `foregroundStyle(by:)` instead595- Rely on unstable identities when chart data can be inserted, removed, or reordered596- Use string values for naturally numeric or date-based axes unless you want categorical behavior597- Stack unrelated series by default just because `BarMark` and `AreaMark` allow it598- Force every tick label to display when collision handling or stride values would be clearer599- Assume selection returns a model object; it only returns the plottable axis value600- Forget that range selection is available only for X and Y axes, not angle selection601602For chart accessibility (VoiceOver, Audio Graph, `AXChartDescriptorRepresentable`), fallback strategies, WWDC sessions, and a full summary checklist, see `charts-accessibility.md`.603