swift-charts by dpearson2699/swift-ios-skills
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill swift-charts使用 Swift Charts 构建面向 iOS 26+ 的数据可视化。在 Chart 容器内组合标记,使用视图修饰符配置坐标轴和比例尺,并对大型数据集使用向量化绘图。
有关扩展模式、无障碍功能和主题指南,请参阅 references/charts-patterns.md。
Identifiable 结构体或使用 id: 键路径。BarMark、LineMark、、、、 或 。广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
PointMarkAreaMarkRuleMarkRectangleMarkSectorMarkChart 容器中。.foregroundStyle(by:)、.symbol(by:)、.lineStyle(by:)。.chartXAxis / .chartYAxis 配置坐标轴。.chartXScale(domain:) / .chartYScale(domain:) 设置比例尺定义域。BarPlot、LinePlot 等)。运行本文件末尾的审查清单。
// 数据驱动初始化(单系列)
Chart(sales) { item in
BarMark(x: .value("Month", item.month), y: .value("Revenue", item.revenue))
}
// 内容闭包初始化(多系列,混合标记)
Chart {
ForEach(seriesA) { item in
LineMark(x: .value("Date", item.date), y: .value("Value", item.value))
.foregroundStyle(.blue)
}
RuleMark(y: .value("Target", 500))
.foregroundStyle(.red)
}
// 自定义 ID 键路径
Chart(data, id: \.category) { item in
BarMark(x: .value("Category", item.category), y: .value("Count", item.count))
}
// 垂直条形图
BarMark(x: .value("Month", item.month), y: .value("Sales", item.sales))
// 按类别堆叠(当同一 x 值映射到多个条形时自动处理)
BarMark(x: .value("Month", item.month), y: .value("Sales", item.sales))
.foregroundStyle(by: .value("Product", item.product))
// 水平条形图
BarMark(x: .value("Sales", item.sales), y: .value("Month", item.month))
// 区间条形图(甘特图)
BarMark(
xStart: .value("Start", item.start),
xEnd: .value("End", item.end),
y: .value("Task", item.task)
)
// 单条线
LineMark(x: .value("Date", item.date), y: .value("Price", item.price))
// 通过前景样式编码实现多系列
LineMark(x: .value("Date", item.date), y: .value("Temp", item.temp))
.foregroundStyle(by: .value("City", item.city))
.interpolationMethod(.catmullRom)
// 使用显式 series 参数实现多系列
LineMark(
x: .value("Date", item.date),
y: .value("Price", item.price),
series: .value("Ticker", item.ticker)
)
PointMark(x: .value("Height", item.height), y: .value("Weight", item.weight))
.foregroundStyle(by: .value("Species", item.species))
.symbol(by: .value("Species", item.species))
.symbolSize(100)
// 堆叠面积图
AreaMark(x: .value("Date", item.date), y: .value("Sales", item.sales))
.foregroundStyle(by: .value("Category", item.category))
// 范围带
AreaMark(
x: .value("Date", item.date),
yStart: .value("Min", item.min),
yEnd: .value("Max", item.max)
)
.opacity(0.3)
RuleMark(y: .value("Target", 9000))
.foregroundStyle(.red)
.lineStyle(StrokeStyle(dash: [5, 3]))
.annotation(position: .top, alignment: .leading) {
Text("Target").font(.caption).foregroundStyle(.red)
}
RectangleMark(x: .value("Hour", item.hour), y: .value("Day", item.day))
.foregroundStyle(by: .value("Intensity", item.intensity))
// 饼图
Chart(data, id: \.name) { item in
SectorMark(angle: .value("Sales", item.sales))
.foregroundStyle(by: .value("Category", item.name))
}
// 环形图
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))
}
// 隐藏坐标轴
.chartXAxis(.hidden)
.chartYAxis(.hidden)
// 自定义坐标轴内容
.chartXAxis {
AxisMarks(values: .stride(by: .month)) { value in
AxisGridLine()
AxisTick()
AxisValueLabel(format: .dateTime.month(.abbreviated))
}
}
// 多个 AxisMarks 组合(网格与标签使用不同的间隔)
.chartXAxis {
AxisMarks(values: .stride(by: .day)) { _ in AxisGridLine() }
AxisMarks(values: .stride(by: .week)) { _ in
AxisTick()
AxisValueLabel(format: .dateTime.week())
}
}
// 坐标轴标签(标题)
.chartXAxisLabel("Time", position: .bottom, alignment: .center)
.chartYAxisLabel("Revenue ($)", position: .leading, alignment: .center)
.chartYScale(domain: 0...100) // 显式数值定义域
.chartYScale(domain: .automatic(includesZero: true)) // 包含零
.chartYScale(domain: 1...10000, type: .log) // 对数比例尺
.chartXScale(domain: ["Mon", "Tue", "Wed", "Thu"]) // 分类排序
BarMark(...).foregroundStyle(.blue) // 静态颜色
BarMark(...).foregroundStyle(by: .value("Category", item.category)) // 数据编码
AreaMark(...).foregroundStyle( // 渐变
.linearGradient(colors: [.blue, .cyan], startPoint: .bottom, endPoint: .top)
)
@State private var selectedDate: Date?
@State private var selectedRange: ClosedRange<Date>?
@State private var selectedAngle: String?
// 点选择
Chart(data) { item in
LineMark(x: .value("Date", item.date), y: .value("Value", item.value))
}
.chartXSelection(value: $selectedDate)
// 范围选择
.chartXSelection(range: $selectedRange)
// 角度选择(饼图/环形图)
.chartAngleSelection(value: $selectedAngle)
Chart(dailyData) { item in
BarMark(x: .value("Date", item.date, unit: .day), y: .value("Steps", item.steps))
}
.chartScrollableAxes(.horizontal)
.chartXVisibleDomain(length: 3600 * 24 * 7) // 显示 7 天
.chartScrollPosition(initialX: latestDate)
.chartScrollTargetBehavior(
.valueAligned(matching: DateComponents(hour: 0), majorAlignment: .page)
)
BarMark(x: .value("Month", item.month), y: .value("Sales", item.sales))
.annotation(position: .top, alignment: .center, spacing: 4) {
Text("\(item.sales, format: .number)").font(.caption2)
}
// 溢出解决方案
.annotation(
position: .top,
overflowResolution: .init(x: .fit(to: .chart), y: .padScale)
) { Text("Label") }
.chartLegend(.hidden) // 隐藏
.chartLegend(position: .bottom, alignment: .center, spacing: 10) // 定位
.chartLegend(position: .bottom) { // 自定义
HStack(spacing: 16) {
ForEach(categories, id: \.self) { cat in
Label(cat, systemImage: "circle.fill").font(.caption)
}
}
}
用于大型数据集(1000 个以上数据点)。接受整个集合或函数。
// 数据驱动
Chart {
BarPlot(sales, x: .value("Month", \.month), y: .value("Revenue", \.revenue))
.foregroundStyle(\.barColor)
}
// 函数绘图:y = f(x)
Chart {
LinePlot(x: "x", y: "y", domain: -5...5) { x in sin(x) }
}
// 参数方程:(x, y) = f(t)
Chart {
LinePlot(x: "x", y: "y", t: "t", domain: 0...(2 * .pi)) { t in
(x: cos(t), y: sin(t))
}
}
在简单值修饰符之前应用基于 KeyPath 的修饰符:
BarPlot(data, x: .value("X", \.x), y: .value("Y", \.y))
.foregroundStyle(\.color) // KeyPath 优先
.opacity(0.8) // 值修饰符其次
// 错误
class ChartModel: ObservableObject {
@Published var data: [Sale] = []
}
struct ChartView: View {
@StateObject private var model = ChartModel()
}
// 正确
@Observable class ChartModel {
var data: [Sale] = []
}
struct ChartView: View {
@State private var model = ChartModel()
}
// 错误 -- 所有点连接成一条线
Chart {
ForEach(allCities) { item in
LineMark(x: .value("Date", item.date), y: .value("Temp", item.temp))
}
}
// 正确 -- 每个城市一条独立的线
Chart {
ForEach(allCities) { item in
LineMark(x: .value("Date", item.date), y: .value("Temp", item.temp))
.foregroundStyle(by: .value("City", item.city))
}
}
// 错误 -- 20 个微小扇区难以辨认
Chart(twentyCategories, id: \.name) { item in
SectorMark(angle: .value("Value", item.value))
}
// 正确 -- 分组为前 5 名 + "其他"
Chart(groupedData, id: \.name) { item in
SectorMark(angle: .value("Value", item.value))
.foregroundStyle(by: .value("Category", item.name))
}
// 错误 -- 坐标轴从 ~95 开始;微小变化看起来很大
Chart(data) {
LineMark(x: .value("Day", $0.day), y: .value("Score", $0.score))
}
// 正确 -- 显式定义域以实现真实表示
Chart(data) {
LineMark(x: .value("Day", $0.day), y: .value("Score", $0.score))
}
.chartYScale(domain: 0...100)
// 错误 -- 静态颜色覆盖了按值编码
BarMark(x: .value("X", item.x), y: .value("Y", item.y))
.foregroundStyle(by: .value("Category", item.category))
.foregroundStyle(.blue)
// 正确 -- 仅使用数据编码
BarMark(x: .value("X", item.x), y: .value("Y", item.y))
.foregroundStyle(by: .value("Category", item.category))
// 错误 -- 创建 10,000 个标记视图;速度慢
Chart(largeDataset) { item in
PointMark(x: .value("X", item.x), y: .value("Y", item.y))
}
// 正确 -- 向量化绘图 (iOS 18+)
Chart {
PointPlot(largeDataset, x: .value("X", \.x), y: .value("Y", \.y))
}
// 错误 -- 在大文本大小时裁剪坐标轴标签
Chart(data) { ... }
.frame(height: 200)
// 正确 -- 自适应尺寸
Chart(data) { ... }
.frame(minHeight: 200, maxHeight: 400)
// 错误 -- 编译器错误
BarPlot(data, x: .value("X", \.x), y: .value("Y", \.y))
.opacity(0.8)
.foregroundStyle(\.color)
// 正确 -- KeyPath 修饰符优先
BarPlot(data, x: .value("X", \.x), y: .value("Y", \.y))
.foregroundStyle(\.color)
.opacity(0.8)
// 错误 -- VoiceOver 用户无法获取上下文
Chart(data) {
BarMark(x: .value("Month", $0.month), y: .value("Sales", $0.sales))
}
// 正确 -- 为每个标记添加无障碍功能
Chart(data) { item in
BarMark(x: .value("Month", item.month), y: .value("Sales", item.sales))
.accessibilityLabel("\(item.month)")
.accessibilityValue("\(item.sales) units sold")
}
Identifiable 或图表使用 id: 键路径@Observable 和 @State,而非 ObservableObjectseries: 参数或 .foregroundStyle(by:)Date?).chartXVisibleDomain(length:) 以定义视口references/charts-patterns.md每周安装量
401
仓库
GitHub 星标数
269
首次出现
2026年3月3日
安全审计
安装于
codex397
kimi-cli394
gemini-cli394
amp394
cline394
github-copilot394
Build data visualizations with Swift Charts targeting iOS 26+. Compose marks inside a Chart container, configure axes and scales with view modifiers, and use vectorized plots for large datasets.
See references/charts-patterns.md for extended patterns, accessibility, and theming guidance.
Identifiable struct or use id: key path.BarMark, LineMark, PointMark, AreaMark, RuleMark, RectangleMark, or SectorMark.Chart container..foregroundStyle(by:), .symbol(by:), .lineStyle(by:)..chartXAxis / .chartYAxis..chartXScale(domain:) / .chartYScale(domain:).BarPlot, LinePlot, etc.).Run through the Review Checklist at the end of this file.
// Data-driven init (single-series)
Chart(sales) { item in
BarMark(x: .value("Month", item.month), y: .value("Revenue", item.revenue))
}
// Content closure init (multi-series, mixed marks)
Chart {
ForEach(seriesA) { item in
LineMark(x: .value("Date", item.date), y: .value("Value", item.value))
.foregroundStyle(.blue)
}
RuleMark(y: .value("Target", 500))
.foregroundStyle(.red)
}
// Custom ID key path
Chart(data, id: \.category) { item in
BarMark(x: .value("Category", item.category), y: .value("Count", item.count))
}
// 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)
)
// 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)
)
PointMark(x: .value("Height", item.height), y: .value("Weight", item.weight))
.foregroundStyle(by: .value("Species", item.species))
.symbol(by: .value("Species", item.species))
.symbolSize(100)
// 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)
RuleMark(y: .value("Target", 9000))
.foregroundStyle(.red)
.lineStyle(StrokeStyle(dash: [5, 3]))
.annotation(position: .top, alignment: .leading) {
Text("Target").font(.caption).foregroundStyle(.red)
}
RectangleMark(x: .value("Hour", item.hour), y: .value("Day", item.day))
.foregroundStyle(by: .value("Intensity", item.intensity))
// 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))
}
// 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)
.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
BarMark(...).foregroundStyle(.blue) // Static color
BarMark(...).foregroundStyle(by: .value("Category", item.category)) // Data encoding
AreaMark(...).foregroundStyle( // Gradient
.linearGradient(colors: [.blue, .cyan], startPoint: .bottom, endPoint: .top)
)
@State private var selectedDate: Date?
@State private var selectedRange: ClosedRange<Date>?
@State private var selectedAngle: String?
// 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 (pie/donut)
.chartAngleSelection(value: $selectedAngle)
Chart(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)
)
BarMark(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") }
.chartLegend(.hidden) // Hide
.chartLegend(position: .bottom, alignment: .center, spacing: 10) // Position
.chartLegend(position: .bottom) { // Custom
HStack(spacing: 16) {
ForEach(categories, id: \.self) { cat in
Label(cat, systemImage: "circle.fill").font(.caption)
}
}
}
Use for large datasets (1000+ points). Accept entire collections or functions.
// 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:
BarPlot(data, x: .value("X", \.x), y: .value("Y", \.y))
.foregroundStyle(\.color) // KeyPath first
.opacity(0.8) // Value modifier second
// WRONG
class ChartModel: ObservableObject {
@Published var data: [Sale] = []
}
struct ChartView: View {
@StateObject private var model = ChartModel()
}
// CORRECT
@Observable class ChartModel {
var data: [Sale] = []
}
struct ChartView: View {
@State private var model = ChartModel()
}
// 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))
}
}
// 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))
}
// 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)
// 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))
// 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))
}
// WRONG -- clips axis labels at large text sizes
Chart(data) { ... }
.frame(height: 200)
// CORRECT -- adaptive sizing
Chart(data) { ... }
.frame(minHeight: 200, maxHeight: 400)
// 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)
// 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")
}
Identifiable or chart uses id: key path@Observable with @State, not ObservableObjectseries: parameter or .foregroundStyle(by:)Date? for date axis).chartXVisibleDomain(length:) for viewportreferences/charts-patterns.mdWeekly Installs
401
Repository
GitHub Stars
269
First Seen
Mar 3, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex397
kimi-cli394
gemini-cli394
amp394
cline394
github-copilot394
iOS Swift开发指南:SwiftUI、Combine、async/await构建高性能原生应用
701 周安装
Medusa 管理员用户创建指南:使用 new-user Skill 快速配置后台账户
410 周安装
LobeHub桌面端开发指南:基于Electron的桌面应用架构与功能实现教程
410 周安装
精益用户体验画布 (Lean UX Canvas v2) 指南:产品经理假设驱动开发与实验验证工具
411 周安装
iOS StoreKit 2 应用内购买与订阅开发指南 - SwiftUI 实现教程
411 周安装
Sentry AI监控配置指南:追踪LLM调用、智能体执行与令牌消耗
411 周安装
macOS SwiftPM应用打包指南:无需Xcode构建、签名、公证全流程
411 周安装