swift-charts について
Swift 開発者向けの Swift Charts 可視化スキルです。2D・3D チャート、マーク、軸、選択、スクロールを扱い、大規模データとアクセシビリティの確認方法も示します。
主な機能
- 01複数のマークによるチャート構成
- 02軸、スケール、凡例の設定
- 03選択、スクロール、注釈の追加
- 04大規模データ向けベクトル化プロット
- 05空間データ向け 3D チャート
- 06アクセシビリティと表示状態の確認
活用シーン
- 01カテゴリ比較や時系列の表示
- 02チャートへのインタラクティブなデータ選択追加
- 031000 件を超えるデータのプロット
- 04空間データや二変数曲面の可視化
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill swift-chartsインストール後、Claude Code で/swift-chartsを入力して読み込まれたか確認します。
同梱ファイルevals/evals.jsonreferences/charts-patterns.md
詳細
Swift Charts
iOS 26+ を対象に、Swift Charts でデータを可視化します。Chart または Chart3D 内にマークを配置し、ビュー修飾子で軸とスケールを設定します。データに応じて、ベクトル化プロットや 3D プロットを使用します。
詳しいパターン、3D チャート、アクセシビリティ、テーマ設定については、references/charts-patterns.md を参照してください。
目次
- ワークフロー
- チャートコンテナー
- マークの種類
- 軸のカスタマイズ
- スケールの設定
- 前景スタイルとエンコーディング
- 選択(iOS 17+)
- スクロール可能なチャート(iOS 17+)
- 注釈
- 凡例
- ベクトル化プロット(iOS 18+)
- 3D チャート(iOS 26+)
- よくある間違い
- レビュー用チェックリスト
- 参考資料
ワークフロー
1. 新しいチャートを作成する
- データを
Identifiable構造体として定義するか、id:キーパスを使用します。 - マークの種類を選択します:
BarMark、LineMark、PointMark、AreaMark、RuleMark、RectangleMark、SectorMark、またはSurfacePlot。 - 2D マークは
Chartで囲みます。Chart3Dは、実際の空間データまたはサーフェスデータにのみ使用します。 - 視覚チャネルをエンコードします:
.foregroundStyle(by:)、.symbol(by:)、.lineStyle(by:)。 .chartXAxis/.chartYAxisで軸を設定します。.chartXScale(domain:)/.chartYScale(domain:)でスケールのドメインを設定します。- 必要に応じて選択、スクロール、注釈を追加します。
- 2D データポイントが 1000 個以上の場合は、ベクトル化プロット(
BarPlot、LinePlotなど)を使用します。 - 代表的な空の状態、通常の状態、高密度の状態、大きな文字、高コントラスト、VoiceOver の各状態を表示します。選択やスクロールがある場合は、それらも動作させます。
- エンコーディング、軸、選択、アクセシビリティのチェックに失敗した場合は、データフィクスチャを復元して一つの層を修正し、装飾を加える前に同じテストマトリックスを再実行します。
2. 既存のチャートコードをレビューする
マークの選択を評価する前に、データの意味を特定します。次に、すべての値がマーク、スケール、軸、スタイル、選択、アクセシビリティ出力を通じてどのように扱われるかを追跡し、新しいチャートで使用したものと同じ検証マトリックスを実行します。
チャートコンテナー
Chart(sales) { item in
BarMark(x: .value("Month", item.month), y: .value("Revenue", item.revenue))
}
単一のコレクションには、データ駆動型のイニシャライザーを使用します。複数の種類のマークや複数の系列にはコンテンツクロージャを使用し、要素が Identifiable でない場合は id: を渡します。複数系列、テーマ設定、アクセシビリティ、3D の完全なレシピについては、references/charts-patterns.md を読み込んでください。
マークの種類
BarMark(iOS 16+)
// 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)
)
LineMark(iOS 16+)
// 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(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+)
// 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(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 は厳密に正の値に使用します。ゼロまたは負の値は、円グラフやドーナツグラフの外でフィルタリング、集計するか、説明を加えてください。
// 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)
)
選択(iOS 17+)
@State private var selectedDate: Date?
@State private var selectedRange: ClosedRange<Date>?
@State private var selectedAngle: Double?
// 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 binds the plottable angle value; derive the category from ranges.
.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 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 {
ForEach(categories, id: \.self) { cat in
Label(cat, systemImage: "circle.fill").font(.caption)
}
}
}
ベクトル化プロット(iOS 18+)
大規模なデータセット(1000 ポイント以上)に使用します。コレクション全体または関数を受け取ります。
// 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))
}
}
KeyPath ベースの修飾子は、単純な値を使う修飾子より先に適用します。
BarPlot(data, x: .value("X", \.x), y: .value("Y", \.y))
.foregroundStyle(\.color) // KeyPath first
.opacity(0.8) // Value modifier second
3D チャート(iOS 26+)
Chart3D は空間データまたは二変量サーフェスに使用します。通常の 2D カテゴリ別チャートや時系列チャートの装飾目的の代替としては使用しません。Chart3D は SurfacePlot に加えて、PointMark、RuleMark、RectangleMark の 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. 複数の折れ線チャートで系列パラメーターがない
// 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))
}
}
2. SectorMark のスライスが多すぎる
// 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))
}
3. ゼロ基準が重要な場合にスケールドメインがない
// 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)
4. 静的な foregroundStyle がデータエンコーディングを上書きする
// 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))
5. データポイントが 10,000 個以上なのに個別のマークを使用する
// 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))
}
6. 固定のグラフの高さによる Dynamic Type の崩れ
// WRONG -- clips axis labels at large text sizes
Chart(data) { ... }
.frame(height: 200)
// CORRECT -- adaptive sizing
Chart(data) { ... }
.frame(minHeight: 200, maxHeight: 400)
7. ベクトル化されたプロットで、値のモディファイアの後に KeyPath モディファイアを指定する
// 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)
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")
}
9. 角度の選択をカテゴリの選択として扱う
chartAngleSelection(value:) は、選択されたプロット可能な角度の値にバインドします。円グラフとドーナツグラフでは、その数値を累積した扇形の範囲に対応付けてから、カテゴリラベルと比較してください。
レビューチェックリスト
- データモデルが
Identifiableを使用しているか、グラフがid:キーパスを使用している - マークの種類が目的に合っている(棒=比較、折れ線=傾向、扇形=割合)
- 複数系列の折れ線で
series:パラメーターまたは.foregroundStyle(by:)を使用している - 軸に適切なラベル、目盛り、グリッド線が設定されている
- 0 を基準とする必要がある場合、スケールドメインを明示的に設定している
- 円グラフ/ドーナツグラフで正の値と5~7個の扇形を使い、「その他」にまとめている
- 選択バインディングの型が軸のデータ型と一致している(日付軸では
Date?) - 円グラフ/ドーナツグラフの角度選択で、数値の角度をカテゴリに対応付けている
- スクロール可能なグラフで、表示領域に
.chartXVisibleDomain(length:)を設定している - 1000点を超えるデータセットでベクトル化されたプロットを使用している
- ベクトル化されたプロットで、KeyPath モディファイアを値のモディファイアより先に適用している
-
Chart3Dを実際の3Dデータまたはサーフェスにのみ使用し、z軸のスケールと視点を確認している - VoiceOver 用にマークへアクセシビリティラベルを追加している
- Dynamic Type とダークモードでグラフをテストしている
- 凡例が表示され、位置が設定されているか、意図的に非表示になっている
- グラフのデータモデルの型が Sendable であることを確認し、@MainActor でグラフのデータを更新している
参考資料
- 応用パターン:references/charts-patterns.md
- Apple のドキュメント:Swift Charts
- Apple のドキュメント:Swift Charts を使ったグラフの作成
- Apple のドキュメント:Swift Charts の更新情報
- Apple のドキュメント:Chart3D
- Apple のドキュメント:SurfacePlot