WWDC Quick Look 💓 By SwiftGGTeam
Explore pie charts and interactivity in Swift Charts

Explore pie charts and interactivity in Swift Charts

元の動画を見る

ハイライト

Swift Charts に SectorMark 円グラフ型、chartXSelection 選択 API、chartScrollableAxes スクロール機能が追加され、データ可視化が静的表示からインタラクティブな探索へ進化しました。

主要内容

Richard はパンケーキ屋台の売上データを通してセッション全体を進めます。6 種類のフレーバー売上比率は円グラフに最適で、365 日分の日次売上はスクロール閲覧が必要、都市間比較データは選択インタラクションの価値を示します。

01:13)円グラフの中核は新しい SectorMark です。極座標系に配置され、扇形の角度はデータ値に比例します。棒グラフからの変換は 2 行の変更だけ — BarMarkSectorMark に、x パラメータを angle に置き換えます。

02:35angularInset で扇形の間隔、cornerRadius で角丸を制御します。innerRadius を半径比(例: 0.618)に設定するとドーナツグラフになります。ドーナツ中央の穴には chartBackground で集計情報を配置できます。

04:24)選択インタラクションが今年の重点です。chartXSelection(value:) 修飾子がすべてのジェスチャー認識を処理し、選択値を状態変数にバインドします。独自のジェスチャー検出は不要です。選択後は RuleMark で垂直インジケータ線、annotation で詳細ポップオーバーを表示できます。

07:07)単一値選択に加え、範囲選択もサポート — chartXSelection(range:) で区間を選べます。デフォルトジェスチャーはプラットフォーム別: iOS は 2 本指タップ、macOS はドラッグ。ChartProxy でタッチ位置をデータ値に変換する完全カスタムジェスチャーも可能です。

07:31)円グラフの選択は chartAngleSelection(value:) を使用 — 選択扇形は自動ハイライト、他は暗くなります。opacity 修飾子で未選択扇形の透明度を制御します。

07:54)スクロール可能なチャートは「データ点が多すぎて画面に収まらない」問題を解決します。chartScrollableAxes(.horizontal) で横スクロール、chartXVisibleDomain(length:) で表示ウィンドウサイズ(秒単位の時間間隔)を設定。chartScrollPosition(x:) で現在のスクロール位置をバインドし、ウィンドウの集計データを表示できます。

08:50)スクロール整列は chartScrollTargetBehavior で値ベースのスナップをサポート。デモでは各日の最初の時間に整列し、大きくスワイプすると majorAlignment で各月の 1 日目にさらに一致させます。月次売上データの閲覧は常に月初で止まります。

詳細

棒グラフから円グラフへ

// 元の棒グラフ
Chart(data, id: \.name) { element in
    BarMark(
        x: .value("Sales", element.sales),
        stacking: .normalized
    )
    .foregroundStyle(by: .value("Name", element.name))
}

// 円グラフに変更 — Mark 型とパラメータ名だけ変更
Chart(data, id: \.name) { element in
    SectorMark(
        angle: .value("Sales", element.sales)
    )
    .foregroundStyle(by: .value("Name", element.name))
}

キーポイント:

  • 角度値は自動的に完全な円に正規化 — 手動でパーセンテージ計算は不要
  • foregroundStyle(by:) がカテゴリごとに色を自動割り当て

洗練されたドーナツグラフ

Chart(data, id: \.name) { element in
    SectorMark(
        angle: .value("Sales", element.sales),
        innerRadius: .ratio(0.618),
        angularInset: 1.5
    )
    .cornerRadius(5)
    .foregroundStyle(by: .value("Name", element.name))
}
.chartBackground { chartProxy in
    GeometryReader { geometry in
        let frame = geometry[chartProxy.plotAreaFrame]
        VStack {
            Text("Most Sold Style")
                .font(.callout)
                .foregroundStyle(.secondary)
            Text(mostSold)
                .font(.title2.bold())
        }
        .position(x: frame.midX, y: frame.midY)
    }
}

キーポイント:

  • innerRadius: .ratio(0.618) で黄金比によりドーナツの厚さを制御
  • angularInset: 1.5 で各扇形を 1.5pt 内側に — 実際の隙間は 3pt
  • chartBackground でチャート描画領域の中央にコンテンツを配置、GeometryReader で中央位置を計算

選択インジケータ付き折れ線グラフ

struct LocationDetailsChart: View {
    @Binding var rawSelectedDate: Date?
    
    var selectedDate: Date? {
        guard let rawSelectedDate else { return nil }
        return data.first?.sales.first(where: {
            let endOfDay = endOfDay(for: $0.day)
            return ($0.day ... endOfDay).contains(rawSelectedDate)
        })?.day
    }
    
    var body: some View {
        Chart {
            ForEach(data) { series in
                ForEach(series.sales, id: \.day) { element in
                    LineMark(
                        x: .value("Day", element.day, unit: .day),
                        y: .value("Sales", element.sales)
                    )
                }
                .foregroundStyle(by: .value("City", series.city))
                .symbol(by: .value("City", series.city))
                .interpolationMethod(.catmullRom)
            }
            
            if let selectedDate {
                RuleMark(
                    x: .value("Selected", selectedDate, unit: .day)
                )
                .foregroundStyle(Color.gray.opacity(0.3))
                .offset(yStart: -10)
                .zIndex(-1)
                .annotation(
                    position: .top, spacing: 0,
                    overflowResolution: .init(
                        x: .fit(to: .chart),
                        y: .disabled
                    )
                ) {
                    ValuePopover(date: selectedDate)
                }
            }
        }
        .chartXSelection(value: $rawSelectedDate)
    }
}

キーポイント:

  • chartXSelection は生の座標値を返す — 計算プロパティで最寄りのデータ点にマッチ
  • RuleMarkzIndex(-1) でインジケータを折れ線の下に描画
  • overflowResolution でチャート境界での注釈動作を制御: X はチャート内、Y ははみ出し可
  • macOS ではホバー、iOS ではタップがデフォルト

スクロール可能な日次売上チャート

Chart {
    ForEach(SalesData.last365Days, id: \.day) {
        BarMark(
            x: .value("Day", $0.day, unit: .day),
            y: .value("Sales", $0.sales)
        )
    }
    .foregroundStyle(.blue)
}
.chartScrollableAxes(.horizontal)
.chartXVisibleDomain(length: 3600 * 24 * 30)
.chartScrollPosition(x: $scrollPosition)
.chartScrollTargetBehavior(
    .valueAligned(
        matching: DateComponents(hour: 0),
        majorAlignment: .matching(DateComponents(day: 1))
    )
)

キーポイント:

  • chartXVisibleDomain(length:) の引数は秒数、30 日 = 3600 * 24 * 30
  • chartScrollPosition で現在位置をバインドし、ウィンドウの集計統計に利用
  • valueAligned でスクロール停止時に時間単位に整列、majorAlignment でページめくり粒度を制御
  • スクロールは SwiftUI ScrollView の拡張に基づき、軸ラベルもコンテンツと同期

円グラフの選択インタラクション

Chart(data, id: \.name) { element in
    SectorMark(
        angle: .value("Sales", element.sales),
        innerRadius: .ratio(0.618),
        angularInset: 1.5
    )
    .cornerRadius(5)
    .foregroundStyle(by: .value("Name", element.name))
    .opacity(element.name == selectedName ? 1.0 : 0.3)
}
.chartAngleSelection(value: $selectedAngle)

キーポイント:

  • chartAngleSelection は極座標チャート専用
  • opacity 修飾子で選択ハイライトと未選択の薄暗化を実現
  • selectedAngle はデータ値に対応させる必要あり — 通常は計算プロパティで変換

重要ポイント

  • フィットネス App に週間アクティビティリングの円グラフ変種を追加: Apple Watch のアクティビティリングは本質的にドーナツグラフです。SectorMark で同様の効果を実現し、中央に総カロリーまたは総時間を表示、扇形タップで運動種別の詳細を表示。

  • 探索可能な家計支出 App を作成: 月次支出をカテゴリ別円グラフで表示し、カテゴリタップで下に時系列折れ線グラフを展開。折れ線グラフで chartXSelection を使い日付を選択し、その日の取引明細を表示。

  • 長期間データにスクロール可能チャートを使用: 株価、天気、健康系 App には数百日分のデータがあります。chartScrollableAxes と chartXVisibleDomain で直近 30 日だけ表示し、左右スワイプで履歴を閲覧。scroll target behavior で月初や月曜に整列。

関連セッション

  • Beyond scroll views — スクロール可能チャートの基盤となる SwiftUI スクロールビューの改善
  • What’s new in SwiftUI — アニメーションとデータフロー改善を含む SwiftUI 全体の新機能
  • Animate with springs — チャートインタラクションの遷移に使えるスプリングアニメーションシステム

コメント

GitHub Issues · utterances