Agent Skills: Swift Charts

Building data visualizations in SwiftUI using Apple's Swift Charts framework

UncategorizedID: arustydev/ai/swift-charts

Repository

aRustyDevLicense: AGPL-3.0
72

Install this agent skill to your local

pnpm dlx add-skill https://github.com/aRustyDev/agents/tree/HEAD/content/plugins/frontend/swiftui-dev/skills/swift-charts

Skill Files

Browse the full folder contents for swift-charts.

Download Skill

Loading file tree…

content/plugins/frontend/swiftui-dev/skills/swift-charts/SKILL.md

Skill Metadata

Name
swift-charts
Description
Building data visualizations in SwiftUI using Apple's Swift Charts framework

Swift Charts

Purpose

Building data visualizations in SwiftUI using Apple's Swift Charts framework.

Basic Charts

Line Chart

import Charts
import SwiftUI

struct TimeSeriesView: View {
    let data: [DataPoint]

    var body: some View {
        Chart(data) { point in
            LineMark(
                x: .value("Date", point.date),
                y: .value("Value", point.value)
            )
        }
        .chartXAxis {
            AxisMarks(values: .stride(by: .month)) { value in
                AxisValueLabel(format: .dateTime.month(.abbreviated))
            }
        }
    }
}

Bar Chart

struct CategoryChart: View {
    let sales: [CategorySales]

    var body: some View {
        Chart(sales) { item in
            BarMark(
                x: .value("Category", item.category),
                y: .value("Sales", item.amount)
            )
            .foregroundStyle(by: .value("Category", item.category))
        }
        .chartLegend(.hidden)
    }
}

Scatter Plot

struct ScatterView: View {
    let points: [Observation]

    var body: some View {
        Chart(points) { point in
            PointMark(
                x: .value("X", point.x),
                y: .value("Y", point.y)
            )
            .foregroundStyle(by: .value("Cluster", point.cluster))
            .symbolSize(point.weight * 10)
        }
    }
}

Area Chart

struct StackedAreaChart: View {
    let data: [CategoryTimeSeries]

    var body: some View {
        Chart(data) { series in
            ForEach(series.values) { point in
                AreaMark(
                    x: .value("Date", point.date),
                    y: .value("Value", point.value)
                )
            }
            .foregroundStyle(by: .value("Category", series.category))
        }
        .chartForegroundStyleScale([
            "Sales": .blue,
            "Expenses": .red,
            "Profit": .green
        ])
    }
}

Advanced Marks

Combined Marks

struct CombinedChart: View {
    let data: [DataPoint]

    var body: some View {
        Chart(data) { point in
            // Line for trend
            LineMark(
                x: .value("Date", point.date),
                y: .value("Value", point.value)
            )
            .foregroundStyle(.blue)

            // Points for individual values
            PointMark(
                x: .value("Date", point.date),
                y: .value("Value", point.value)
            )
            .foregroundStyle(.blue)

            // Rule for average
            RuleMark(y: .value("Average", average))
                .foregroundStyle(.red.opacity(0.5))
                .lineStyle(StrokeStyle(dash: [5, 5]))
        }
    }

    var average: Double {
        data.map(\.value).reduce(0, +) / Double(data.count)
    }
}

Range Marks

struct RangeChart: View {
    let data: [RangeData]

    var body: some View {
        Chart(data) { item in
            // Show range
            RectangleMark(
                x: .value("Category", item.category),
                yStart: .value("Min", item.min),
                yEnd: .value("Max", item.max)
            )
            .opacity(0.3)

            // Show median
            PointMark(
                x: .value("Category", item.category),
                y: .value("Median", item.median)
            )
        }
    }
}

Interactivity

Selection

struct InteractiveChart: View {
    let data: [DataPoint]
    @State private var selectedDate: Date?

    var body: some View {
        Chart(data) { point in
            LineMark(
                x: .value("Date", point.date),
                y: .value("Value", point.value)
            )

            if let selected = selectedDate,
               let point = data.first(where: { Calendar.current.isDate($0.date, inSameDayAs: selected) }) {
                RuleMark(x: .value("Selected", point.date))
                    .foregroundStyle(.gray.opacity(0.3))

                PointMark(
                    x: .value("Date", point.date),
                    y: .value("Value", point.value)
                )
                .foregroundStyle(.red)
                .symbolSize(100)
            }
        }
        .chartXSelection(value: $selectedDate)
        .chartOverlay { proxy in
            GeometryReader { geometry in
                if let selected = selectedDate,
                   let point = data.first(where: { Calendar.current.isDate($0.date, inSameDayAs: selected) }),
                   let position = proxy.position(forX: point.date) {
                    VStack {
                        Text(point.date, format: .dateTime.month().day())
                        Text(point.value, format: .number)
                            .bold()
                    }
                    .padding(8)
                    .background(.ultraThinMaterial)
                    .cornerRadius(8)
                    .position(x: position, y: 20)
                }
            }
        }
    }
}

Scrolling Charts

struct ScrollableChart: View {
    let data: [DataPoint]
    @State private var scrollPosition: Date = .now

    var body: some View {
        Chart(data) { point in
            LineMark(
                x: .value("Date", point.date),
                y: .value("Value", point.value)
            )
        }
        .chartScrollableAxes(.horizontal)
        .chartXVisibleDomain(length: 3600 * 24 * 30) // 30 days
        .chartScrollPosition(x: $scrollPosition)
    }
}

Styling

Custom Colors

Chart(data) { point in
    BarMark(
        x: .value("Category", point.category),
        y: .value("Value", point.value)
    )
    .foregroundStyle(by: .value("Category", point.category))
}
.chartForegroundStyleScale([
    "Category A": Color.blue,
    "Category B": Color.green,
    "Category C": Color.orange
])

Axis Customization

Chart(data) { point in
    LineMark(
        x: .value("Date", point.date),
        y: .value("Value", point.value)
    )
}
.chartXAxis {
    AxisMarks(preset: .aligned) { value in
        AxisGridLine()
        AxisTick()
        AxisValueLabel(format: .dateTime.month())
    }
}
.chartYAxis {
    AxisMarks(position: .leading) { value in
        AxisGridLine()
        AxisValueLabel {
            if let intValue = value.as(Int.self) {
                Text("$\(intValue)")
            }
        }
    }
}

Plot Area Styling

Chart(data) { point in
    LineMark(
        x: .value("X", point.x),
        y: .value("Y", point.y)
    )
}
.chartPlotStyle { plotArea in
    plotArea
        .background(.gray.opacity(0.1))
        .border(.gray, width: 1)
}

Performance (Large Datasets)

Vectorized Plots (WWDC24)

// For datasets > 1000 points
struct LargeDatasetChart: View {
    let points: [Point]  // Thousands of points

    var body: some View {
        Chart {
            PointPlot(points, x: \.x, y: \.y)
                .foregroundStyle(.blue)
        }
    }
}

Data Aggregation

struct AggregatedChart: View {
    let rawData: [DataPoint]

    var aggregatedData: [DataPoint] {
        // Aggregate to reasonable number of points
        Dictionary(grouping: rawData) { point in
            Calendar.current.startOfDay(for: point.date)
        }
        .map { date, points in
            DataPoint(
                date: date,
                value: points.map(\.value).reduce(0, +) / Double(points.count)
            )
        }
        .sorted(by: { $0.date < $1.date })
    }

    var body: some View {
        Chart(aggregatedData) { point in
            LineMark(
                x: .value("Date", point.date),
                y: .value("Value", point.value)
            )
        }
    }
}

Chart3D (macOS 26+ / iOS 20+)

import Charts

struct Scatter3DView: View {
    let points: [Point3D]

    var body: some View {
        Chart3D(points) { point in
            PointMark3D(
                x: .value("X", point.x),
                y: .value("Y", point.y),
                z: .value("Z", point.z)
            )
            .foregroundStyle(by: .value("Cluster", point.cluster))
        }
        .chart3DStyle(.interactive) // Allows rotation
    }
}

Common Patterns

Empty State

struct ChartWithEmptyState: View {
    let data: [DataPoint]

    var body: some View {
        if data.isEmpty {
            ContentUnavailableView(
                "No Data",
                systemImage: "chart.bar.xaxis",
                description: Text("Add some data to see the chart")
            )
        } else {
            Chart(data) { point in
                LineMark(
                    x: .value("Date", point.date),
                    y: .value("Value", point.value)
                )
            }
        }
    }
}

Loading State

struct ChartWithLoading: View {
    let data: [DataPoint]?
    let isLoading: Bool

    var body: some View {
        ZStack {
            if let data {
                Chart(data) { point in
                    LineMark(
                        x: .value("Date", point.date),
                        y: .value("Value", point.value)
                    )
                }
            }

            if isLoading {
                ProgressView()
            }
        }
    }
}

Related Skills

  • duckdb-swift: Data source for charts
  • swiftui-data-flow: Reactive chart updates
  • grape-graphs: Force-directed graph visualization