All skills
dpearson2699 avatar

/swift-charts

@cf3fe87

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.

Use this Skill: https://skilld.dev/gh/dpearson2699/swift-ios-skills/swift-charts

This session only. Nothing lands on disk.

referencescharts-patterns.md

≈6.2k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Swift Charts Patterns Reference

Extended patterns, accessibility guidance, and theming for Swift Charts on iOS 26+. Import Charts in every file that uses these APIs.

import SwiftUI
import Charts

Contents

Data Modeling

Use @Observable for chart data models. Pair with @State in views.

@Observable
class SalesModel {
    var monthlySales: [MonthlySale] = []

    func load() async {
        monthlySales = await SalesService.fetchMonthlySales()
    }
}

struct MonthlySale: Identifiable {
    let id = UUID()
    let month: Date
    let revenue: Double
    let category: String
}
struct SalesDashboard: View {
    @State private var model = SalesModel()

    var body: some View {
        Chart(model.monthlySales) { item in
            BarMark(
                x: .value("Month", item.month, unit: .month),
                y: .value("Revenue", item.revenue)
            )
            .foregroundStyle(by: .value("Category", item.category))
        }
        .task { await model.load() }
    }
}

Bar Chart Patterns

Simple vertical bars

Chart(data) { item in
    BarMark(
        x: .value("Department", item.department),
        y: .value("Revenue", item.revenue)
    )
}

Stacked bars (automatic)

When multiple bars share the same x value, they stack automatically:

Chart(data) { item in
    BarMark(
        x: .value("Quarter", item.quarter),
        y: .value("Sales", item.sales)
    )
    .foregroundStyle(by: .value("Product", item.product))
}

Grouped bars

Use .position(by:) to place bars side by side instead of stacking:

Chart(data) { item in
    BarMark(
        x: .value("Quarter", item.quarter),
        y: .value("Sales", item.sales)
    )
    .foregroundStyle(by: .value("Product", item.product))
    .position(by: .value("Product", item.product))
}

Horizontal bars

Swap the x and y axes:

Chart(data) { item in
    BarMark(
        x: .value("Sales", item.sales),
        y: .value("Region", item.region)
    )
}
.chartYAxis {
    AxisMarks { _ in
        AxisValueLabel()
    }
}

Normalized stacked bars (100%)

Chart(data) { item in
    BarMark(
        x: .value("Quarter", item.quarter),
        y: .value("Sales", item.sales),
        stacking: .normalized
    )
    .foregroundStyle(by: .value("Product", item.product))
}

Bar with annotation

Chart(data) { item in
    BarMark(
        x: .value("Month", item.month),
        y: .value("Revenue", item.revenue)
    )
    .annotation(position: .top, alignment: .center, spacing: 4) {
        Text(item.revenue, format: .currency(code: "USD").precision(.fractionLength(0)))
            .font(.caption2)
    }
}

Gantt chart (interval bars)

Chart(tasks) { task in
    BarMark(
        xStart: .value("Start", task.startDate),
        xEnd: .value("End", task.endDate),
        y: .value("Task", task.name)
    )
    .foregroundStyle(by: .value("Status", task.status))
}

Line Chart Patterns

Single line with points

Chart(data) { item in
    LineMark(
        x: .value("Date", item.date),
        y: .value("Price", item.price)
    )
    PointMark(
        x: .value("Date", item.date),
        y: .value("Price", item.price)
    )
    .symbolSize(30)
}

Multi-series lines

Chart(temperatures) { item in
    LineMark(
        x: .value("Date", item.date),
        y: .value("Temp", item.temperature)
    )
    .foregroundStyle(by: .value("City", item.city))
    .symbol(by: .value("City", item.city))
}

Line with area fill

Chart(data) { item in
    AreaMark(
        x: .value("Date", item.date),
        y: .value("Value", item.value)
    )
    .foregroundStyle(
        .linearGradient(
            colors: [.blue.opacity(0.3), .blue.opacity(0.05)],
            startPoint: .top,
            endPoint: .bottom
        )
    )
    LineMark(
        x: .value("Date", item.date),
        y: .value("Value", item.value)
    )
    .foregroundStyle(.blue)
}

Interpolation methods

Method Use Case
.linear Default; straight segments between points
.monotone Smooth curve that preserves monotonicity
.catmullRom Smooth general-purpose curve
.cardinal Smooth with adjustable tension
.stepStart Step function starting at data point
.stepCenter Step function centered on data point
.stepEnd Step function ending at data point
LineMark(x: .value("X", item.x), y: .value("Y", item.y))
    .interpolationMethod(.monotone)

Sparkline (minimal inline chart)

Chart(recentData) { item in
    LineMark(
        x: .value("Time", item.time),
        y: .value("Value", item.value)
    )
    .interpolationMethod(.catmullRom)
}
.chartXAxis(.hidden)
.chartYAxis(.hidden)
.chartLegend(.hidden)
.frame(width: 80, height: 30)

Pie and Donut Chart Patterns (SectorMark, iOS 17+)

Use strictly positive values for sectors. Filter, aggregate, or show zero and negative values outside the pie or donut so angular sizes remain meaningful.

Basic pie chart

Chart(products, id: \.name) { item in
    SectorMark(angle: .value("Sales", item.sales))
        .foregroundStyle(by: .value("Product", item.name))
}

Donut chart with golden ratio inner radius

Chart(products, id: \.name) { item in
    SectorMark(
        angle: .value("Sales", item.sales),
        innerRadius: .ratio(0.618),
        outerRadius: .inset(10),
        angularInset: 1
    )
    .cornerRadius(4)
    .foregroundStyle(by: .value("Product", item.name))
}

Donut chart with center label

Chart(products, id: \.name) { item in
    SectorMark(
        angle: .value("Sales", item.sales),
        innerRadius: .ratio(0.618),
        angularInset: 1
    )
    .cornerRadius(4)
    .foregroundStyle(by: .value("Product", item.name))
}
.chartBackground { _ in
    VStack {
        Text("Total")
            .font(.caption)
            .foregroundStyle(.secondary)
        Text("\(totalSales, format: .number)")
            .font(.title2.bold())
    }
}

Angular selection on donut

struct ProductSales: Identifiable {
    let id = UUID()
    let name: String
    let sales: Double
}

@State private var selectedAngle: Double?

var selectedProduct: ProductSales? {
    guard let selectedAngle else { return nil }
    var runningTotal = 0.0

    return products.first { product in
        let range = runningTotal..<(runningTotal + product.sales)
        runningTotal += product.sales
        return range.contains(selectedAngle)
    }
}

Chart(products, id: \.name) { item in
    SectorMark(
        angle: .value("Sales", item.sales),
        innerRadius: .ratio(0.618),
        angularInset: 1
    )
    .cornerRadius(4)
    .foregroundStyle(by: .value("Product", item.name))
    .opacity(selectedProduct == nil || selectedProduct?.name == item.name ? 1.0 : 0.4)
}
.chartAngleSelection(value: $selectedAngle)

chartAngleSelection(value:) binds the selected plottable angle value, not the sector label. Convert that value through cumulative sector ranges before using it to highlight or annotate a category.

Grouping small slices

Limit pie/donut charts to 5-7 positive-value sectors. Group the rest into "Other":

func groupSmallSlices(_ data: [CategorySales], topN: Int = 5) -> [CategorySales] {
    let sorted = data.sorted { $0.sales > $1.sales }
    let top = Array(sorted.prefix(topN))
    let otherTotal = sorted.dropFirst(topN).reduce(0) { $0 + $1.sales }
    guard otherTotal > 0 else { return top }
    return top + [CategorySales(name: "Other", sales: otherTotal)]
}

Combined Chart Patterns

Line + area (trend with fill)

Chart(data) { item in
    AreaMark(
        x: .value("Date", item.date),
        yStart: .value("Min", item.low),
        yEnd: .value("Max", item.high)
    )
    .foregroundStyle(.blue.opacity(0.15))

    LineMark(
        x: .value("Date", item.date),
        y: .value("Average", item.average)
    )
    .foregroundStyle(.blue)
    .lineStyle(StrokeStyle(lineWidth: 2))
}

Bar + threshold rule

Chart {
    ForEach(data) { item in
        BarMark(
            x: .value("Month", item.month),
            y: .value("Revenue", item.revenue)
        )
    }
    RuleMark(y: .value("Target", targetRevenue))
        .foregroundStyle(.red)
        .lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 3]))
        .annotation(position: .top, alignment: .leading) {
            Text("Target: \(targetRevenue, format: .number)")
                .font(.caption)
                .foregroundStyle(.red)
        }
}

Scatter + trend line

Chart {
    ForEach(data) { item in
        PointMark(
            x: .value("Experience", item.yearsExperience),
            y: .value("Salary", item.salary)
        )
        .opacity(0.6)
    }
    LinePlot(x: "Experience", y: "Salary", domain: 0...20) { x in
        baseSalary + x * salaryPerYear  // linear trend
    }
    .foregroundStyle(.red)
    .lineStyle(StrokeStyle(lineWidth: 1.5, dash: [4, 2]))
}

Chart Selection with Overlay Annotation

Show a tooltip at the selected position using chartOverlay:

@State private var selectedDate: Date?

var body: some View {
    Chart(data) { item in
        LineMark(
            x: .value("Date", item.date),
            y: .value("Value", item.value)
        )
        if let selectedDate,
           let match = data.first(where: { Calendar.current.isDate($0.date, inSameDayAs: selectedDate) }) {
            RuleMark(x: .value("Selected", match.date))
                .foregroundStyle(.secondary)
            PointMark(
                x: .value("Date", match.date),
                y: .value("Value", match.value)
            )
            .symbolSize(60)
            .annotation(position: .top) {
                Text("\(match.value, format: .number)")
                    .font(.caption)
                    .padding(4)
                    .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 4))
            }
        }
    }
    .chartXSelection(value: $selectedDate)
}

Scrollable Chart with Visible Domain

@State private var scrollPosition: Date?

var body: some View {
    Chart(dailySteps) { item in
        BarMark(
            x: .value("Date", item.date, unit: .day),
            y: .value("Steps", item.steps)
        )
    }
    .chartScrollableAxes(.horizontal)
    .chartXVisibleDomain(length: 3600 * 24 * 7) // 7 days
    .chartScrollPosition(x: $scrollPosition)
    .chartScrollTargetBehavior(
        .valueAligned(matching: DateComponents(hour: 0), majorAlignment: .page)
    )
    .chartXAxis {
        AxisMarks(values: .stride(by: .day)) { value in
            AxisGridLine()
            AxisValueLabel(format: .dateTime.weekday(.abbreviated))
        }
    }
}

Function Plotting (LinePlot, iOS 18+)

Standard function y = f(x)

Chart {
    LinePlot(x: "x", y: "y", domain: -2 * .pi ... 2 * .pi) { x in
        sin(x)
    }
    .foregroundStyle(.blue)
}
.chartYScale(domain: -1.5...1.5)

Parametric function (x, y) = f(t)

Chart {
    LinePlot(x: "x", y: "y", t: "t", domain: 0 ... 2 * .pi) { t in
        (x: cos(t), y: sin(t))
    }
}
.chartXScale(domain: -1.5...1.5)
.chartYScale(domain: -1.5...1.5)

Range area function

Chart {
    AreaPlot(x: "x", yStart: "min", yEnd: "max", domain: 0...10) { x in
        (yStart: sin(x) - 0.5, yEnd: sin(x) + 0.5)
    }
    .foregroundStyle(.blue.opacity(0.2))
}

3D Charts and Surfaces (Chart3D, iOS 26+)

Use Chart3D when the data has a real third dimension, such as (x, y, z) points, 3D regions, or a bivariate surface. Keep ordinary category comparison, time series, and proportions in 2D charts because they are easier to label, compare, and make accessible.

SurfacePlot for bivariate functions

@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)

3D point cloud

Chart3D(points) { point in
    PointMark(
        x: .value("Width", point.x),
        y: .value("Height", point.y),
        z: .value("Depth", point.z)
    )
    .foregroundStyle(by: .value("Cluster", point.cluster))
}
.chart3DCameraProjection(.perspective)

3D review notes

  • Confirm the z dimension is meaningful and labeled; do not use depth only for decoration.
  • Set explicit x/y/z domains when users need stable comparisons across states.
  • Bind Chart3DPose when users need to inspect the scene interactively.
  • Use SurfacePlot for y = f(x, z) surfaces; use 3D mark initializers for observed data points or regions.

Accessibility

Automatic VoiceOver support

Swift Charts provides automatic VoiceOver descriptions for chart elements. The framework reads axis labels and values to visually impaired users without additional code. Ensure .value("Label", ...) strings are descriptive.

Custom accessibility labels

Chart(data) { item in
    BarMark(
        x: .value("Month", item.month),
        y: .value("Sales", item.sales)
    )
    .accessibilityLabel("Sales for \(item.month)")
    .accessibilityValue("\(item.sales) units sold")
}

Accessibility on vectorized plots (KeyPath-based)

BarPlot(data, x: .value("Month", \.month), y: .value("Sales", \.sales))
    .accessibilityLabel(\.accessibilityDescription)
    .accessibilityValue(\.formattedSales)

Audio graphs

The system automatically generates audio representations of chart data for VoiceOver users. Use clear, consistent data labels to ensure audio graphs convey meaningful patterns.

Best practices

  • Use descriptive strings in .value("Label", ...) -- these become VoiceOver labels.
  • Add .accessibilityLabel and .accessibilityValue for context beyond raw numbers.
  • Test with VoiceOver enabled: navigate the chart and verify each element is announced.
  • Avoid .accessibilityHidden(true) on data-bearing marks.

Dynamic Type and Color Considerations

Dynamic Type

Charts automatically adjust axis label sizes with Dynamic Type. Avoid fixed frame heights that clip labels at larger text sizes.

// WRONG -- clips at large text sizes
Chart(data) { ... }
    .frame(height: 200)

// CORRECT -- adaptive height
Chart(data) { ... }
    .frame(minHeight: 200)
    .frame(maxHeight: 400)

Test charts at the "Accessibility Extra Extra Extra Large" text size to verify axis labels, annotations, and legends remain readable.

Color

  • Avoid encoding meaning solely in color. Pair .foregroundStyle(by:) with .symbol(by:) or .lineStyle(by:) for distinguishability.
  • Use system colors that adapt to both light and dark modes.
  • Test with color blindness simulations in the Accessibility Inspector.
LineMark(x: .value("Date", item.date), y: .value("Value", item.value))
    .foregroundStyle(by: .value("Category", item.category))
    .symbol(by: .value("Category", item.category))
    .lineStyle(by: .value("Category", item.category))

Performance: Vectorized Plots for Large Datasets

For datasets exceeding 1000 data points, use vectorized plot types instead of individual marks. Vectorized plots accept entire collections and render efficiently.

When to use vectorized plots

Data Points Recommended Approach
< 100 Individual marks (BarMark, LineMark, etc.)
100 - 1000 Either approach; profile if performance matters
> 1000 Vectorized plots (BarPlot, LinePlot, etc.)

Data-driven vectorized plot

struct SensorReading: Identifiable {
    let id: Int
    let timestamp: Date
    let temperature: Double
    var color: Color { temperature > 30 ? .red : .blue }
    var accessibilityDescription: Text {
        Text("\(timestamp.formatted(.dateTime.hour().minute())): \(temperature, specifier: "%.1f") degrees")
    }
}

Chart {
    LinePlot(
        readings,
        x: .value("Time", \.timestamp),
        y: .value("Temperature", \.temperature)
    )
    .foregroundStyle(.blue)
}

KeyPath modifier ordering

Apply KeyPath-based modifiers before simple-value modifiers:

// WRONG
BarPlot(data, x: .value("X", \.x), y: .value("Y", \.y))
    .opacity(0.8)                // value modifier
    .foregroundStyle(\.color)    // KeyPath -- compiler error

// CORRECT
BarPlot(data, x: .value("X", \.x), y: .value("Y", \.y))
    .foregroundStyle(\.color)    // KeyPath first
    .opacity(0.8)                // value modifier second

Available vectorized plot types

Plot Type Mark Equivalent Available From
BarPlot BarMark iOS 18+
LinePlot LineMark iOS 18+
PointPlot PointMark iOS 18+
AreaPlot AreaMark iOS 18+
RulePlot RuleMark iOS 18+
RectanglePlot RectangleMark iOS 18+
SectorPlot SectorMark iOS 18+

Dark Mode and Theming

Automatic adaptation

Swift Charts inherits the current color scheme automatically. System colors (.blue, .orange, .green) adapt to light and dark modes without extra code.

Custom color palettes

Use .chartForegroundStyleScale to define a consistent palette:

Chart(data) { item in
    BarMark(
        x: .value("Category", item.category),
        y: .value("Value", item.value)
    )
    .foregroundStyle(by: .value("Category", item.category))
}
.chartForegroundStyleScale([
    "Electronics": .blue,
    "Clothing": .purple,
    "Food": .orange,
    "Books": .green,
    "Other": .gray
])

Background and plot area styling

Chart(data) { ... }
.chartPlotStyle { plotArea in
    plotArea
        .background(.quaternary.opacity(0.3))
        .border(.quaternary, width: 0.5)
}

Axis styling

.chartXAxisStyle { axis in
    axis.background(.blue.opacity(0.05))
}

Testing dark mode

Always preview charts in both light and dark color schemes. In Xcode previews:

#Preview {
    ChartView()
        .preferredColorScheme(.dark)
}

Verify:

  • Axis labels and grid lines are readable.
  • Data colors maintain sufficient contrast.
  • Annotations and legend text adapt properly.

Heat Map Pattern

Chart(heatMapData) { item in
    RectangleMark(
        x: .value("Hour", item.hour),
        y: .value("Day", item.day)
    )
    .foregroundStyle(by: .value("Count", item.count))
}
.chartForegroundStyleScale(range: Gradient(colors: [.blue, .yellow, .red]))

Stacking Methods

Method Behavior
.standard Default. Regions stack on top showing absolute values.
.normalized Scales to 0-100% proportional view.
.center Baseline centered (streamgraph).
.unstacked Overlapping; no stacking.
AreaMark(
    x: .value("Date", item.date),
    y: .value("Revenue", item.revenue),
    stacking: .normalized
)
.foregroundStyle(by: .value("Category", item.category))

MarkDimension Options

Dimension Description
.automatic Framework decides
.fixed(CGFloat) Exact pixel size
.inset(CGFloat) Inset from available space
.ratio(CGFloat) Proportion of available space (0...1)

Use for width, height on BarMark and innerRadius, outerRadius on SectorMark.


Symbol Configuration

Built-in shapes

circle, square, triangle, diamond, pentagon, plus, cross, asterisk

PointMark(x: .value("X", item.x), y: .value("Y", item.y))
    .symbol(.diamond)
    .symbolSize(80)

Data-driven symbol encoding

PointMark(x: .value("X", item.x), y: .value("Y", item.y))
    .symbol(by: .value("Category", item.category))

Custom symbol view

PointMark(x: .value("X", item.x), y: .value("Y", item.y))
    .symbol {
        Image(systemName: "star.fill")
            .font(.caption2)
    }

ChartProxy and Coordinate Conversion

Use chartOverlay or chartBackground to access ChartProxy:

.chartOverlay { proxy in
    GeometryReader { geometry in
        Rectangle()
            .fill(.clear)
            .contentShape(Rectangle())
            .gesture(
                DragGesture()
                    .onChanged { value in
                        let origin = geometry[proxy.plotAreaFrame].origin
                        let location = CGPoint(
                            x: value.location.x - origin.x,
                            y: value.location.y - origin.y
                        )
                        if let date: Date = proxy.value(atX: location.x) {
                            selectedDate = date
                        }
                    }
            )
    }
}

Key ChartProxy methods

Method Purpose
position(forX:) Data value to screen x-coordinate
position(forY:) Data value to screen y-coordinate
value(atX:as:) Screen x-coordinate to data value
value(atY:as:) Screen y-coordinate to data value
plotAreaSize Size of the plot area
plotAreaFrame Anchor for the plot area frame

Quick Reference: Chart View Modifiers

Axes

  • chartXAxis(_:) / chartXAxis(content:)
  • chartYAxis(_:) / chartYAxis(content:)
  • chartXAxisLabel(...) / chartYAxisLabel(...)
  • chartXAxisStyle(content:) / chartYAxisStyle(content:)

Scales

  • chartXScale(domain:range:type:) and variants
  • chartYScale(domain:range:type:) and variants
  • chartZScale(domain:range:type:) for Chart3D
  • chartForegroundStyleScale(_:) -- custom color mapping

3D charts (iOS 26+)

  • Chart3D with SurfacePlot or 3D mark initializers
  • chart3DPose(_:) for interactive pose binding
  • chart3DCameraProjection(_:) for orthographic/perspective projection

Legend

  • chartLegend(_:) -- visibility
  • chartLegend(position:alignment:spacing:) -- positioning
  • chartLegend(position:alignment:spacing:content:) -- custom content

Selection (iOS 17+)

  • chartXSelection(value:) / chartXSelection(range:)
  • chartYSelection(value:) / chartYSelection(range:)
  • chartAngleSelection(value:) -- for SectorMark

Scrolling (iOS 17+)

  • chartScrollableAxes(_:)
  • chartXVisibleDomain(length:) / chartYVisibleDomain(length:)
  • chartScrollPosition(initialX:) / chartScrollPosition(x:)
  • chartScrollTargetBehavior(_:)

Overlay and Background

  • chartOverlay(alignment:content:) -- with ChartProxy
  • chartBackground(alignment:content:) -- with ChartProxy
  • chartPlotStyle(content:) -- plot area styling

Apple Documentation Links

Source: SKILL.md on GitHub

No alerts17d5 checks · Risk SAFE
  • Gen Agent Trust Hub17d

    The skill provides comprehensive guidance and code patterns for building data visualizations with Swift Charts on iOS. It covers various chart types, accessibility, performance, and interaction models without any detected security risks.

  • Socket17d

    No alerts

  • Snyk17d

    Risk: LOW · No issues

  • Runlayer6mo

    2 files scanned · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at cf3fe87. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Steadyupdated 3 months ago
  • swift
  • ios
  • charts
  • data-visualization
  • swiftui
  • bar-chart
  • line-chart
  • pie-chart
  • animations

README badge

README badge for dpearson2699/swift-ios-skills/swift-charts

Implement data visualizations with Swift Charts using marks (bar, line, area, point, pie, donut) inside a Chart container, configure axes and scales, and use vectorized plots for datasets over 1000 points. Covers selection, scrolling, annotations, legends, and specialized charts like heat maps, Gantt charts, and stacked bars on iOS 16+.

Generated from the current SKILL.md.

What iOS versions does Swift Charts support?
iOS 16+ for basic marks (BarMark, LineMark, PointMark, AreaMark, RuleMark, RectangleMark). Selection and scrollable charts require iOS 17+. Vectorized plots (BarPlot, LinePlot, AreaPlot, PointPlot) require iOS 18+.
What chart types can I build?
Bar, line, area, point, rule, rectangle, sector (pie and donut), and vectorized plots. The skill also covers specialized charts like heat maps, Gantt charts, stacked/grouped bars, sparklines, and threshold lines.
When should I use vectorized plots instead of individual marks?
Use vectorized plots (BarPlot, LinePlot, etc.) for datasets with 1000+ data points. Individual marks create separate view instances and become slow at large scales.
Does this skill cover chart interactivity?
Yes. It includes selection (point and range selection on iOS 17+), scrollable charts with configurable visible domains, and annotations with overflow resolution.
Can I customize axes and legends?
Yes. You can hide axes, set custom AxisMarks with different intervals for grid vs. labels, add axis titles, position legends, or create custom legend layouts.

Generated from the current SKILL.md. These answers refresh after source changes.