mcpField
S
swift-charts

swift-charts

github.com/dpearson2699データ・データベース1.2k4.0k 回インストール13 週間前に更新
リポジトリ

swift-charts について

Swift 開発者向けの Swift Charts 可視化スキルです。2D・3D チャート、マーク、軸、選択、スクロールを扱い、大規模データとアクセシビリティの確認方法も示します。

主な機能

  1. 01複数のマークによるチャート構成
  2. 02軸、スケール、凡例の設定
  3. 03選択、スクロール、注釈の追加
  4. 04大規模データ向けベクトル化プロット
  5. 05空間データ向け 3D チャート
  6. 06アクセシビリティと表示状態の確認

活用シーン

  1. 01カテゴリ比較や時系列の表示
  2. 02チャートへのインタラクティブなデータ選択追加
  3. 031000 件を超えるデータのプロット
  4. 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 を参照してください。

目次

ワークフロー

1. 新しいチャートを作成する
  1. データを Identifiable 構造体として定義するか、id: キーパスを使用します。
  2. マークの種類を選択します:BarMark、LineMark、PointMark、AreaMark、 RuleMark、RectangleMark、SectorMark、または SurfacePlot。
  3. 2D マークは Chart で囲みます。Chart3D は、実際の空間データまたはサーフェスデータにのみ使用します。
  4. 視覚チャネルをエンコードします:.foregroundStyle(by:)、.symbol(by:)、.lineStyle(by:)。
  5. .chartXAxis / .chartYAxis で軸を設定します。
  6. .chartXScale(domain:) / .chartYScale(domain:) でスケールのドメインを設定します。
  7. 必要に応じて選択、スクロール、注釈を追加します。
  8. 2D データポイントが 1000 個以上の場合は、ベクトル化プロット(BarPlot、LinePlot など)を使用します。
  9. 代表的な空の状態、通常の状態、高密度の状態、大きな文字、高コントラスト、VoiceOver の各状態を表示します。選択やスクロールがある場合は、それらも動作させます。
  10. エンコーディング、軸、選択、アクセシビリティのチェックに失敗した場合は、データフィクスチャを復元して一つの層を修正し、装飾を加える前に同じテストマトリックスを再実行します。
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 でグラフのデータを更新している

参考資料

コメント