swift-charts

swift-charts

熱門

使用 Swift Charts 實作、審查或改善資料視覺化。適用於建立長條圖、折線圖、區域圖、散佈圖、圓餅圖、甜甜圈圖或 iOS 26 的 3D 圖表;新增圖表選取、捲動、註解、軸線、尺度、圖例或 foregroundStyle 分組;使用 BarPlot、LinePlot、AreaPlot、PointPlot、Chart3D 或 SurfacePlot 繪製函數;或建立熱圖、甘特圖、群組長條圖、走勢圖、閾值線或空間視覺化。

932星標
47分支
更新於 2026/7/15
SKILL.md
唯讀
名稱
swift-charts
描述

使用 Swift Charts 實作、審查或改善資料視覺化。適用於建立長條圖、折線圖、區域圖、散佈圖、圓餅圖、甜甜圈圖或 iOS 26 的 3D 圖表;新增圖表選取、捲動、註解、軸線、尺度、圖例或 foregroundStyle 分組;使用 BarPlot、LinePlot、AreaPlot、PointPlot、Chart3D 或 SurfacePlot 繪製函數;或建立熱圖、甘特圖、群組長條圖、走勢圖、閾值線或空間視覺化。

Swift Charts

使用 Swift Charts 建立資料視覺化,目標 iOS 26 以上。在 ChartChart3D 內組合標記,使用檢視修飾器設定軸線與尺度,並在資料需要時使用向量化繪圖或 3D 繪圖。

請參閱 references/charts-patterns.md 以取得進階模式、3D 圖表、無障礙與主題設定指引。

目錄

工作流程

1. 建立新圖表

  1. 將資料定義為 Identifiable 結構體,或使用 id: 鍵路徑。
  2. 選擇標記類型:BarMarkLineMarkPointMarkAreaMarkRuleMarkRectangleMarkSectorMarkSurfacePlot
  3. 將 2D 標記包在 Chart 中;僅在真正的空間或曲面資料時使用 Chart3D
  4. 編碼視覺通道:.foregroundStyle(by:).symbol(by:).lineStyle(by:)
  5. 使用 .chartXAxis / .chartYAxis 設定軸線。
  6. 使用 .chartXScale(domain:) / .chartYScale(domain:) 設定尺度範圍。
  7. 視需要新增選取、捲動或註解。
  8. 若 2D 資料點超過 1000 筆,使用向量化繪圖(BarPlotLinePlot 等)。
  9. 呈現代表性的空白、典型、密集、大字型、高對比及 VoiceOver 狀態;若有選取或捲動功能,也一併測試。
  10. 若編碼、軸線、選取或無障礙檢查失敗,還原資料固定值,修正一層後重新執行相同矩陣,再新增裝飾。

2. 審查現有圖表程式碼

在判斷標記選擇前,先識別資料語意。然後追蹤每個值經過標記、尺度、軸線、樣式、選取及無障礙輸出的過程;執行與新圖表相同的驗證矩陣。

圖表容器

Chart(sales) { item in
    BarMark(x: .value("Month", item.month), y: .value("Revenue", item.revenue))
}

對單一集合使用資料驅動的初始化器。對混合標記或多個序列使用內容閉包,並在元素不為 Identifiable 時傳入 id:。載入 references/charts-patterns.md 以取得完整的混合序列、主題設定、無障礙及 3D 範例。

標記類型

BarMark (iOS 16+)

// 垂直長條圖
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 (iOS 16+)

// 單一折線
LineMark(x: .value("Date", item.date), y: .value("Price", item.price))

// 透過 foregroundStyle 編碼的多序列
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 (iOS 16+)

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 (iOS 16+)

// 堆疊區域圖
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 (iOS 16+)

RuleMark(y: .value("Target", 9000))
    .foregroundStyle(.red)
    .lineStyle(StrokeStyle(dash: [5, 3]))
    .annotation(position: .top, alignment: .leading) {
        Text("Target").font(.caption).foregroundStyle(.red)
    }

RectangleMark (iOS 16+)

RectangleMark(x: .value("Hour", item.hour), y: .value("Day", item.day))
    .foregroundStyle(by: .value("Intensity", item.intensity))

SectorMark (iOS 17+)

使用 SectorMark 處理嚴格正數值;在圓餅圖或甜甜圈圖外部過濾、聚合或說明零值/負值。

// 圓餅圖
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)
)

選取 (iOS 17+)

@State private var selectedDate: Date?
@State private var selectedRange: ClosedRange<Date>?
@State private var selectedAngle: Double?

// 點選取
Chart(data) { item in
    LineMark(x: .value("Date", item.date), y: .value("Value", item.value))
}
.chartXSelection(value: $selectedDate)

// 範圍選取
.chartXSelection(range: $selectedRange)

// 角度選取綁定可繪圖的角度值;從範圍推導類別。
.chartAngleSelection(value: $selectedAngle)

可捲動圖表 (iOS 17+)

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 {
        ForEach(categories, id: \.self) { cat in
            Label(cat, systemImage: "circle.fill").font(.caption)
        }
    }
}

向量化繪圖 (iOS 18+)

適用於大型資料集(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)                // 再值修飾器

3D 圖表 (iOS 26+)

使用 Chart3D 處理空間資料或雙變數曲面,而非作為一般 2D 類別或時間序列圖表的裝飾性替代。Chart3D 接受 SurfacePlot 以及 PointMarkRuleMarkRectangleMark 的 3D 初始化器。

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

常見錯誤

1. 多折線圖缺少 series 參數

// 錯誤 -- 所有點連成一條線
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))
    }
}

2. SectorMark 切片過多

// 錯誤 -- 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))
}

3. 零基線重要時缺少尺度範圍

// 錯誤 -- 軸線從 ~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)

4. 靜態 foregroundStyle 覆蓋資料編碼

// 錯誤 -- 靜態顏色覆蓋依值編碼
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))

5. 對 10,000+ 資料點使用個別標記

// 錯誤 -- 建立 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))
}

6. 固定圖表高度破壞 Dynamic Type

// 錯誤 -- 大字型時裁切軸線標籤
Chart(data) { ... }
    .frame(height: 200)

// 正確 -- 自適應尺寸
Chart(data) { ... }
    .frame(minHeight: 200, maxHeight: 400)

7. 向量化繪圖上值修飾器先於 KeyPath 修飾器

// 錯誤 -- 編譯錯誤
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)

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")
}

9. 將角度選取視為類別選取

chartAngleSelection(value:) 綁定選取的可繪圖角度值。對於圓餅圖和甜甜圈圖,在與類別標籤比較之前,先將該數值透過累積扇形範圍進行映射。

審查檢查清單

  • [ ] 資料模型使用 Identifiable 或圖表使用 id: 鍵路徑
  • [ ] 標記類型符合目標(長條=比較,折線=趨勢,扇形=比例)
  • [ ] 多序列折線使用 series: 參數或 .foregroundStyle(by:)
  • [ ] 軸線已設定適當的標籤、刻度與網格線
  • [ ] 零基線重要時已明確設定尺度範圍
  • [ ] 圓餅圖/甜甜圈圖使用正值、5-7 個切片,並有「其他」分組
  • [ ] 選取綁定型別符合軸線資料型別(日期軸使用 Date?
  • [ ] 圓餅圖/甜甜圈圖的角度選取將數值角度值映射回類別
  • [ ] 可捲動圖表已設定 .chartXVisibleDomain(length:) 以定義視埠
  • [ ] 資料集超過 1000 點時使用向量化繪圖
  • [ ] 向量化繪圖上 KeyPath 修飾器先於值修飾器套用
  • [ ] Chart3D 僅用於真正的 3D 資料或曲面,並已審查 z 尺度與姿勢
  • [ ] 已為標記新增無障礙標籤以支援 VoiceOver
  • [ ] 圖表已測試 Dynamic Type 與深色模式
  • [ ] 圖例可見且已定位,或刻意隱藏
  • [ ] 確保圖表資料模型型別為 Sendable;在 @MainActor 上更新圖表資料

參考資料