ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Swift Chartsの円グラフとインタラクティブ性の詳細
Swift Chartsは円を描きます。フレームワークへの最新の改善で、アプリで円グラフやドーナツグラフを作成する準備をしましょう。グラフをスクロール可能にする方法を学び、データの追加的な詳細を明らかにするためのグラフ選択APIを探索し、追加的なインタラクティブ性を有効にすることで、グラフをさらに楽しくする方法を見つけましょう。
関連する章
- 0:20 - Pie charts
- 4:22 - Selection
- 7:49 - Scrolling
リソース
関連ビデオ
WWDC23
-
ダウンロード
♪ ♪
こんにちは Richardです Swift Chartsの エキサイティングな新機能について お話しできることを嬉しく思います 円グラフ 選択機能 そしてスクロールです 円グラフから始めましょう Swift Chartsはあらゆる種類の データ視覚化を作成するための 構成可能でカスタマイズ可能な 構成要素を提供します Swift Chartsシリーズに新しく 美しい円グラフが追加されました 円グラフは合計値がさまざまなカテゴリに どのように構成されるかを シンプルで見慣れた図形で示します これは友人のフードトラックの パンケーキの販売データ を視覚化したグラフです 円グラフには軸がないため 精度が重要ではない カジュアルな表示に最適です どのように完全な円を 構成するか広く理解すると 部分と全体の関係を 直感的に視覚化できます 円グラフが好まれる重要な理由の1つは 丸くて親しみやすい形状です すでによく知っているマークベースの composition syntaxを使用して 円グラフを作成できます 新しいマークタイプ SectorMarkの導入です SectorMarkは パイのスライスを表します それは極空間に位置しています この極ではなく極座標系です Sectorのサイズは Sectorが表す値に比例します 半径に沿って Sectorの外観をカスタマイズできます 内側の半径を大きくすると 円グラフはドーナツグラフになります SectorMarkを使用すると あらゆる種類のグラフを簡単に作成できます 例を示しましょう 私の友人のパンケーキ フードトラックビジネスでは 日々の売上が大きく活性化して成長しました 昨年は6種類のパンケーキを販売しました この夏私は彼らの販売アプリの改善を 支援するという新たな課題に取り組みました
最も売れているスタイルのパンケーキを 視覚化したグラフから始めます アプリには単純な積み上げ棒グラフ が用意されています これにはBarMarkが付けられ 売上はX次元に沿って積み上げられます カテゴリデータであるため カテゴリは各積み上げ棒の フォアグラウンドスタイル として反映されます このグラフで十分ですが 画面上の空きスペースを活用するために 円グラフに変換しましょう データを目立たせることができます BarMarkと引数xを SectorMarkとangleに 切り替えるだけです とても簡単です!
SectorMarkでは販売数量を 表すために角度を使用します 円グラフに指定した角度の値は 完全な円に自動的に正規化されます スタイルのカスタマイズを 適用することもできます angularInsetをSectorに設定して Sector間にギャップを作成できます ここではSectorの角度インセットを 1.5 ポイントに設定しているため 2つのSector間のギャップは インセットの2倍で幅は3ポイントです 角の半径も設定して 円周がきれいに丸みを帯びるようにしました わずか数行のコードだけで 素晴らしいものになります 別の外観を試すために これをドーナツグラフに変えましょう
内側の半径を 円の全半径の比率に設定しました 私にとって黄金比は ちょうどいいように見えますが ドーナツの厚さは 別の方が好みかもしれません 販売されたすべてのパンケーキの中で 現在チャート上に表示されているのは 断然の勝者cachapaです しかしドーナツグラフは 中央が空洞になっているため テキストをグラフの中央に 移動したいと考えています chartBackgroundに テキストを配置します テキストがドーナツの穴の 中央に配置されるように いくつかの位置計算を使用します
これで見栄えの良い ドーナツグラフが完成しました これが円グラフとドーナツグラフです データに印象を与えるのに 最適な方法であり 大画面で見ると非常に美しいです 次に「選択」による グラフの双方向機能について 詳しく説明したいと思います グラフでインタラクティブ性を 有効にすることで 追加の詳細を 徐々に開示することになります インタラクティブ性によりユーザーは タッチなどのさまざまな形式の入力で 自然にデータを探索することができます
「選択」はチャートと 直接通信する方法です 心拍数グラフなどの Appleが設計したグラフは その完璧な例です 軸に沿って点を選択すると チャートに追加情報が表示されます このアイデアをパンケーキ販売アプリに 取り入れてみましょう
アプリ内の1つのグラフは 2つの都市の曜日ごとの 平均売上高を視覚化します
このチャートで値の選択を有効にして 選択日に販売されたパンケーキの数を示す ポップオーバーによって 詳細な販売数を表示します
このようにグラフが定義されます 各都市にはデータシリーズがあります シリーズの各要素には 曜日と販売数があります 次に線は都市名によって スタイル設定されます chartOverlay modifierについては すでにご存知かもしれません このmodifierでSwiftUIビューを オーバーレイしてジェスチャをキャプチャします iOS17では新しいchartXSelection modifierを使用できます すべてのジェスチャ認識を処理し 選択した値をバインディングに保存します
選択modifierにより X軸に沿った生の日付値が得られるので 私の折れ線グラフでは それをデータポイントと照合する 計算プロパティを定義します グラフを拡張して 値が選択されたときに ポップオーバーを表示しましょう
値を選択すると 選択インジケーターとして 縦罫マークを追加します ルールマークがラインマークの 後ろに留まるように Zインデックスをデフォルトの 0値よりも低く設定しました 次に選択インジケーターの上に ポップオーバーを作成しましょう これはカスタムSwiftUIビューを 使用して注釈として行うことができます 通常注釈はチャート内に配置されますが この場合ポップオーバーは グラフの境界を越えて広がります ここで注釈のオーバーフロー解決が 必要になります
X軸ではポップオーバーが グラフの水平方向の 境界を越えないよう グラフにフィットさせます Y軸では注釈が グラフのすぐ上に来るように オーバーフロー解決を無効にします
選択バインディングと 注釈付きのルールマークを使用すると これで対話型の 折れ線グラフが作成されました Swift ChartsはmacOSでの選択も サポートしており 値選択のデフォルトは ホバージェスチャです
単一の値の選択に加えて このチャート選択modifierの バリアントでは範囲を選択できます iOSではデフォルトは2本指の タップジェスチャです macOSではこれは ドラッグジェスチャです Swift ChartsではChartProxyを使い ジェスチャの位置に基づいて 値を選択できるよう選択用の カスタムジェスチャを提供できます
X座標とY座標のグラフ以外にも グラフ値の選択は円グラフや ドーナツグラフでもシームレスに機能します Sectorをタップして ハイライト表示するのはとても楽しいです
選択とはチャート内の追加情報を 明らかにすることがすべてでした インタラクティブ性のもう1つの 重要な部分はデータのナビゲーションです スクロールについて話しましょう
1年間の毎日のパンケーキの売上を 視覚化するグラフを作成したいと思います 365日すべてを画面に 収めることは非現実的なので スクロール可能にする必要があります それにはchartScrollableAxes modifierを呼び出します chartXVisibleDomain を使用して時間間隔として 30日間の表示ウィンドウを設定します 現在表示されているドメインで パンケーキの総売上を表示するには chartScrollPositionを使用して 現在の日付をバインディングに保存します これで指を使って スクロールできるようになりました
プロットをスクロールできるだけでなく 軸のコンテンツも一緒にスクロールでき 非常にスムーズです スクロールはいくつかの 方法でカスタマイズできます たとえばスクロールを常に 日付単位にスナップしたいとします これによりスクロール動作が始まります ScrollTargetBehaviorは SwiftUIおよびSwift Charts に新しく追加され スクロールビュー のコンテンツを値に揃えます
私が望んでいたスナップ動作については 1日の最初の時間に合わせて設定しました majorAlignmentは スワイプ動作を定義することで さらに一歩カスタマイズします ここでは月の最初の日に設定することで スワイプしてチャートのページを移動しても いつも毎月1日に戻っています
スクロール可能なグラフは 最新の機能強化の一部 に基づいて構築され SwiftUIでビューをスクロールします 詳細については「Beyond ScrollViews」を 必ずご確認ください Swift Chartはデータを視覚化する 無限の可能性を提供します X座標とY座標のグラフを超えて 円グラフもAPIシリーズの一部になり Appleが設計したグラフを作成します 円グラフはシンプルかつ 強力な視覚化です データの一部と全体の関係を表す 場合に最も効果的です 選択やスクロールなどの 双方向機能により データの視覚化に まったく新しい次元が可能になります ユーザーは運転席に座って データを探索できます パイやドーナツのグラフを おいしく味わってください ♪ ♪
-
-
2:06 - Stacked bar chart
Chart(data, id: \.name) { element in BarMark( x: .value("Sales", element.sales), stacking: .normalized ) .foregroundStyle(by: .value("Name", element.name)) } .chartXAxis(.hidden)
-
2:44 - Pie chart
Chart(data, id: \.name) { element in SectorMark( angle: .value("Sales", element.sales) ) .foregroundStyle(by: .value("Name", element.name)) }
-
3:05 - Pie chart with angular inset
Chart(data, id: \.name) { element in SectorMark( angle: .value("Sales", element.sales), angularInset: 1.5 ) .foregroundStyle(by: .value("Name", element.name)) }
-
3:06 - Pie chart with corner radius
Chart(data, id: \.name) { element in SectorMark( angle: .value("Sales", element.sales), angularInset: 1.5 ) .cornerRadius(5) .foregroundStyle(by: .value("Name", element.name)) }
-
3:33 - Donut chart
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)) }
-
4:02 - Donut chart with text in the center
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()) .foregroundColor(.primary) } .position(x: frame.midX, y: frame.midY) } }
-
5:14 - Chart visualizing average sales by city
struct LocationDetailsChart: View { ... 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) } } ... } }
-
5:39 - Chart selection modifier
struct LocationDetailsChart: View { @Binding var rawSelectedDate: Date? 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) } } .chartXSelection(value: $rawSelectedDate) } }
-
5:47 - Processing raw selected date from chart selection binding
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) } } .chartXSelection(value: $rawSelectedDate) } }
-
6:06 - Rule mark as selection indicator
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) ) } } if let selectedDate { RuleMark( x: .value("Selected", selectedDate, unit: .day) ) .foregroundStyle(Color.gray.opacity(0.3)) .offset(yStart: -10) .zIndex(-1) } } .chartXSelection(value: $rawSelectedDate)
-
6:20 - Selection popover
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) ) } } 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 ) ) { valueSelectionPopover } } } .chartXSelection(value: $rawSelectedDate)
-
7:07 - Range selection
Chart(data) { series in ForEach(series.sales, id: \.day) { element in LineMark( x: .value("Day", element.day, unit: .day), y: .value("Sales", element.sales) ) } ... } .chartXSelection(value: $rawSelectedDate) .chartXSelection(range: $rawSelectedRange)
-
7:22 - Overriding default selection gesture
Chart(data) { series in ForEach(series.sales, id: \.day) { element in LineMark( x: .value("Day", element.day, unit: .day), y: .value("Sales", element.sales) ) } ... } .chartXSelection(value: $rawSelectedDate) .chartGesture { proxy in DragGesture(minimumDistance: 0) .onChanged { proxy.selectXValue(at: $0.location.x) } .onEnded { _ in selectedDate = nil } }
-
7:31 - Selection in pie charts and donut charts
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)
-
7:54 - Daily sales chart
Chart { ForEach(SalesData.last365Days, id: \.day) { BarMark( x: .value("Day", $0.day, unit: .day), y: .value("Sales", $0.sales) ) } .foregroundStyle(.blue) }
-
8:07 - Daily sales chart with a scrollable axis
Chart { ForEach(SalesData.last365Days, id: \.day) { BarMark( x: .value("Day", $0.day, unit: .day), y: .value("Sales", $0.sales) ) } .foregroundStyle(.blue) } .chartScrollableAxes(.horizontal)
-
8:11 - Setting the visible domain for a scrollable chart
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)
-
8:18 - Chart scroll position
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)
-
8:50 - Snapping in a scrolling chart
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))))
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。