ハイライト
Swift Charts に SectorMark 円グラフ型、chartXSelection 選択 API、chartScrollableAxes スクロール機能が追加され、データ可視化が静的表示からインタラクティブな探索へ進化しました。
主要内容
Richard はパンケーキ屋台の売上データを通してセッション全体を進めます。6 種類のフレーバー売上比率は円グラフに最適で、365 日分の日次売上はスクロール閲覧が必要、都市間比較データは選択インタラクションの価値を示します。
(01:13)円グラフの中核は新しい SectorMark です。極座標系に配置され、扇形の角度はデータ値に比例します。棒グラフからの変換は 2 行の変更だけ — BarMark を SectorMark に、x パラメータを angle に置き換えます。
(02:35)angularInset で扇形の間隔、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 内側に — 実際の隙間は 3ptchartBackgroundでチャート描画領域の中央にコンテンツを配置、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は生の座標値を返す — 計算プロパティで最寄りのデータ点にマッチRuleMarkのzIndex(-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 * 30chartScrollPositionで現在位置をバインドし、ウィンドウの集計統計に利用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