Agents swift-charts
Building data visualizations in SwiftUI using Apple's Swift Charts framework
install
source · Clone the upstream repo
git clone https://github.com/aRustyDev/agents
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/aRustyDev/agents "$T" && mkdir -p ~/.claude/skills && cp -r "$T/content/plugins/frontend/swiftui-dev/skills/swift-charts" ~/.claude/skills/arustydev-agents-swift-charts && rm -rf "$T"
manifest:
content/plugins/frontend/swiftui-dev/skills/swift-charts/SKILL.mdsource content
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