Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Implement, review, or improve data visualizations using Swift Charts. Use when building bar, line, area, point, pie, donut, or iOS 26 3D charts; when adding chart selection, scrolling, annotations, axes, scales, legends, or foregroundStyle grouping; when plotting functions with BarPlot, LinePlot, AreaPlot, PointPlot, Chart3D, or SurfacePlot; or when creating heat maps, Gantt charts, grouped bars, sparklines, threshold lines, or spatial visualizations.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 85% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 121% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 155% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 219% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 178% | 0% |
Build data visualizations with Swift Charts targeting iOS 26+. Compose marks inside Chart or Chart3D, configure axes and scales with view modifiers, and use vectorized plots or 3D plots when the data calls for them.
See references/charts-patterns.md for extended patterns, 3D charts, accessibility, and theming guidance.
Identifiable struct or use id: key path.BarMark, LineMark, PointMark, AreaMark,RuleMark, RectangleMark, SectorMark, or SurfacePlot.
Chart; use Chart3D only for real spatial or surface data..foregroundStyle(by:), .symbol(by:), .lineStyle(by:)..chartXAxis / .chartYAxis..chartXScale(domain:) / .chartYScale(domain:).BarPlot, LinePlot, etc.).Identify the data semantics before judging mark choice. Then trace every value through mark, scale, axis, style, selection, and accessibility output; run the same validation matrix used for new charts.
swiftChart(sales) { item in BarMark(x: .value("Month", item.month), y: .value("Revenue", item.revenue)) }
Use the data-driven initializer for a single collection. Use the content closure for mixed marks or multiple series, and pass id: when elements are not Identifiable. Load references/charts-patterns.md for complete mixed-series, theming, accessibility, and 3D recipes.
swift// Vertical bar BarMark(x: .value("Month", item.month), y: .value("Sales", item.sales)) // Stacked by category (automatic when same x maps to multiple bars) BarMark(x: .value("Month", item.month), y: .value("Sales", item.sales)) .foregroundStyle(by: .value("Product", item.product)) // Horizontal bar BarMark(x: .value("Sales", item.sales), y: .value("Month", item.month)) // Interval bar (Gantt chart) BarMark( xStart: .value("Start", item.start), xEnd: .value("End", item.end), y: .value("Task", item.task) )
swift// Single line LineMark(x: .value("Date", item.date), y: .value("Price", item.price)) // Multi-series via foregroundStyle encoding LineMark(x: .value("Date", item.date), y: .value("Temp", item.temp)) .foregroundStyle(by: .value("City", item.city)) .interpolationMethod(.catmullRom) // Multi-series with explicit series parameter LineMark( x: .value("Date", item.date), y: .value("Price", item.price), series: .value("Ticker", item.ticker) )
swiftPointMark(x: .value("Height", item.height), y: .value("Weight", item.weight)) .foregroundStyle(by: .value("Species", item.species)) .symbol(by: .value("Species", item.species)) .symbolSize(100)
swift// Stacked area AreaMark(x: .value("Date", item.date), y: .value("Sales", item.sales)) .foregroundStyle(by: .value("Category", item.category)) // Range band AreaMark( x: .value("Date", item.date), yStart: .value("Min", item.min), yEnd: .value("Max", item.max) ) .opacity(0.3)
swiftRuleMark(y: .value("Target", 9000)) .foregroundStyle(.red) .lineStyle(StrokeStyle(dash: [5, 3])) .annotation(position: .top, alignment: .leading) { Text("Target").font(.caption).foregroundStyle(.red) }
swiftRectangleMark(x: .value("Hour", item.hour), y: .value("Day", item.day)) .foregroundStyle(by: .value("Intensity", item.intensity))
Use SectorMark for strictly positive values; filter, aggregate, or explain zero/negative values outside the pie or donut.
swift// Pie chart Chart(data, id: \.name) { item in SectorMark(angle: .value("Sales", item.sales)) .foregroundStyle(by: .value("Category", item.name)) } // Donut chart Chart(data, id: \.name) { item in SectorMark( angle: .value("Sales", item.sales), innerRadius: .ratio(0.618), outerRadius: .inset(10), angularInset: 1 ) .cornerRadius(4) .foregroundStyle(by: .value("Category", item.name)) }
swift// Hide axes .chartXAxis(.hidden) .chartYAxis(.hidden) // Custom axis content .chartXAxis { AxisMarks(values: .stride(by: .month)) { value in AxisGridLine() AxisTick() AxisValueLabel(format: .dateTime.month(.abbreviated)) } } // Multiple AxisMarks compositions (different intervals for grid vs. labels) .chartXAxis { AxisMarks(values: .stride(by: .day)) { _ in AxisGridLine() } AxisMarks(values: .stride(by: .week)) { _ in AxisTick() AxisValueLabel(format: .dateTime.week()) } } // Axis labels (titles) .chartXAxisLabel("Time", position: .bottom, alignment: .center) .chartYAxisLabel("Revenue ($)", position: .leading, alignment: .center)
swift.chartYScale(domain: 0...100) // Explicit numeric domain .chartYScale(domain: .automatic(includesZero: true)) // Include zero .chartYScale(domain: 1...10000, type: .log) // Logarithmic scale .chartXScale(domain: ["Mon", "Tue", "Wed", "Thu"]) // Categorical ordering
swiftBarMark(...).foregroundStyle(.blue) // Static color BarMark(...).foregroundStyle(by: .value("Category", item.category)) // Data encoding AreaMark(...).foregroundStyle( // Gradient .linearGradient(colors: [.blue, .cyan], startPoint: .bottom, endPoint: .top) )
swift@State private var selectedDate: Date? @State private var selectedRange: ClosedRange<Date>? @State private var selectedAngle: Double? // Point selection Chart(data) { item in LineMark(x: .value("Date", item.date), y: .value("Value", item.value)) } .chartXSelection(value: $selectedDate) // Range selection .chartXSelection(range: $selectedRange) // Angular selection binds the plottable angle value; derive the category from ranges. .chartAngleSelection(value: $selectedAngle)
swiftChart(dailyData) { item in BarMark(x: .value("Date", item.date, unit: .day), y: .value("Steps", item.steps)) } .chartScrollableAxes(.horizontal) .chartXVisibleDomain(length: 3600 * 24 * 7) // 7 days visible .chartScrollPosition(initialX: latestDate) .chartScrollTargetBehavior( .valueAligned(matching: DateComponents(hour: 0), majorAlignment: .page) )
swiftBarMark(x: .value("Month", item.month), y: .value("Sales", item.sales)) .annotation(position: .top, alignment: .center, spacing: 4) { Text("\(item.sales, format: .number)").font(.caption2) } // Overflow resolution .annotation( position: .top, overflowResolution: .init(x: .fit(to: .chart), y: .padScale) ) { Text("Label") }
swift.chartLegend(.hidden) // Hide .chartLegend(position: .bottom, alignment: .center, spacing: 10) // Position .chartLegend(position: .bottom) { // Custom HStack { ForEach(categories, id: \.self) { cat in Label(cat, systemImage: "circle.fill").font(.caption) } } }
Use for large datasets (1000+ points). Accept entire collections or functions.
swift// Data-driven Chart { BarPlot(sales, x: .value("Month", \.month), y: .value("Revenue", \.revenue)) .foregroundStyle(\.barColor) } // Function plotting: y = f(x) Chart { LinePlot(x: "x", y: "y", domain: -5...5) { x in sin(x) } } // Parametric: (x, y) = f(t) Chart { LinePlot(x: "x", y: "y", t: "t", domain: 0...(2 * .pi)) { t in (x: cos(t), y: sin(t)) } }
Apply KeyPath-based modifiers before simple-value modifiers:
swiftBarPlot(data, x: .value("X", \.x), y: .value("Y", \.y)) .foregroundStyle(\.color) // KeyPath first .opacity(0.8) // Value modifier second
Use Chart3D for spatial data or bivariate surfaces, not as a decorative replacement for ordinary 2D categorical or time-series charts. Chart3D accepts SurfacePlot plus 3D initializers of PointMark, RuleMark, and RectangleMark.
swift@State private var pose: Chart3DPose = .default Chart3D { SurfacePlot(x: "x", y: "y", z: "z") { x, z in sin(2 * x) * cos(2 * z) } .foregroundStyle(.heightBased) } .chartXScale(domain: -2...2) .chartYScale(domain: -1...1) .chartZScale(domain: -2...2) .chart3DPose($pose)
swift// WRONG -- all points connect into one line Chart { ForEach(allCities) { item in LineMark(x: .value("Date", item.date), y: .value("Temp", item.temp)) } } // CORRECT -- separate lines per city Chart { ForEach(allCities) { item in LineMark(x: .value("Date", item.date), y: .value("Temp", item.temp)) .foregroundStyle(by: .value("City", item.city)) } }
swift// WRONG -- 20 tiny sectors are unreadable Chart(twentyCategories, id: \.name) { item in SectorMark(angle: .value("Value", item.value)) } // CORRECT -- group into top 5 + "Other" Chart(groupedData, id: \.name) { item in SectorMark(angle: .value("Value", item.value)) .foregroundStyle(by: .value("Category", item.name)) }
swift// WRONG -- axis starts at ~95; small changes look dramatic Chart(data) { LineMark(x: .value("Day", $0.day), y: .value("Score", $0.score)) } // CORRECT -- explicit domain for honest representation Chart(data) { LineMark(x: .value("Day", $0.day), y: .value("Score", $0.score)) } .chartYScale(domain: 0...100)
swift// WRONG -- static color overrides by-value encoding BarMark(x: .value("X", item.x), y: .value("Y", item.y)) .foregroundStyle(by: .value("Category", item.category)) .foregroundStyle(.blue) // CORRECT -- use only the data encoding BarMark(x: .value("X", item.x), y: .value("Y", item.y)) .foregroundStyle(by: .value("Category", item.category))
swift// WRONG -- creates 10,000 mark views; slow Chart(largeDataset) { item in PointMark(x: .value("X", item.x), y: .value("Y", item.y)) } // CORRECT -- vectorized plot (iOS 18+) Chart { PointPlot(largeDataset, x: .value("X", \.x), y: .value("Y", \.y)) }
swift// WRONG -- clips axis labels at large text sizes Chart(data) { ... } .frame(height: 200) // CORRECT -- adaptive sizing Chart(data) { ... } .frame(minHeight: 200, maxHeight: 400)
swift// WRONG -- compiler error BarPlot(data, x: .value("X", \.x), y: .value("Y", \.y)) .opacity(0.8) .foregroundStyle(\.color) // CORRECT -- KeyPath modifiers first BarPlot(data, x: .value("X", \.x), y: .value("Y", \.y)) .foregroundStyle(\.color) .opacity(0.8)
swift// WRONG -- VoiceOver users get no context Chart(data) { BarMark(x: .value("Month", $0.month), y: .value("Sales", $0.sales)) } // CORRECT -- add per-mark accessibility Chart(data) { item in BarMark(x: .value("Month", item.month), y: .value("Sales", item.sales)) .accessibilityLabel("\(item.month)") .accessibilityValue("\(item.sales) units sold") }
chartAngleSelection(value:) binds the selected plottable angle value. For pie and donut charts, map that numeric value through cumulative sector ranges before comparing it to a category label.
Identifiable or chart uses id: key pathseries: parameter or .foregroundStyle(by:)Date? for date axis).chartXVisibleDomain(length:) for viewportChart3D used only for real 3D data or surfaces, with z scale and pose reviewedOther measured skills in the registry, with their headline benchmark lift.