WWDC Quick Look 💓 By SwiftGGTeam
Bring Swift Charts to the third dimension

Bring Swift Charts to the third dimension

元の動画を見る

ハイライト

Swift Charts において、iOS 26、macOS 26、visionOS 26 で Chart3DSurfacePlot が追加されました。PointMarkz 軸を与えるだけで、回転可能な 3D 散布図が描画できます。


主要内容

ペンギンのデータセットには、くちばしの長さ、ひれの長さ、体重の 3 つの次元があります。3 種類(Chinstrap、Gentoo、Adélie)の違いを把握するには、これまでは 2D 散布図を 3 枚描く必要がありました。ひれの長さ対体重、くちばしの長さ対体重、くちばしの長さ対ひれの長さ、といった具合です。1 枚の図に示せるのは 2 つの属性の関係だけなので、開発者は頭の中で 3 枚の図を組み合わせて、全体像を把握する必要がありました。この「分割表示による分析」はコストが高く、特にデータそのものが 3 次元である場合、2D への投影によって重要なクラスタの形状が失われてしまいます。

iOS 26、macOS 26、visionOS 26 では、Swift Charts に 3D 表示のサポートが追加されました。ChartChart3D に変更し、PointMark に Z 値を加えるだけで、3 枚の 2D 図が 1 枚の回転可能な 3D 散布図に集約されます。3 種類が即座に 3 つの塊に分かれ、側面から見れば任意の 2 変量関係に戻ることもできます。新たに導入された SurfacePlot は、LinePlot の 3D 版です。(Double, Double) -> Double というクロージャを受け取り、XZ 平面上でサンプリングして連続的な曲面を生成します。散布図の上に重ねて線形回帰を可視化するのにぴったりです。


詳細

2D から 3D への移行は、API の変更がほとんどありません。変更点は 3 箇所だけです。ChartChart3D にする、PointMarkz パラメータを追加する、軸ラベルに chartZAxisLabel を追加する、というものです(03:28)。

struct PenguinChart: View {
  var body: some View {
    Chart3D(penguins) { penguin in
      PointMark(
        x: .value("Flipper Length", penguin.flipperLength),
        y: .value("Weight", penguin.weight),
        z: .value("Beak Length", penguin.beakLength)
      )
      .foregroundStyle(by: .value("Species", penguin.species))
    }
    .chartXAxisLabel("Flipper Length (mm)")
    .chartYAxisLabel("Weight (kg)")
    .chartZAxisLabel("Beak Length (mm)")
    .chartXScale(domain: 160...240, range: -0.5...0.5)
    .chartYScale(domain: 2...7, range: -0.5...0.5)
    .chartZScale(domain: 30...60, range: -0.5...0.5)
  }
}

ポイント:

  • Chart3DChart を直接置き換えられ、イニシャライザのシグネチャも同じなので、既存コードの移行コストは低いです。
  • PointMark に追加される 3 つ目のパラメータ z の型も PlottableValue であり、foregroundStyle(by:) と組み合わせることで Species ごとに自動的に配色できます。
  • chartXScale / chartYScale / chartZScale はいずれも domainrange を受け取ります。domain はデータの範囲、range はグラフ内の正規化座標(ここではいずれも -0.5...0.5)であり、3 軸の幾何学的な比率を揃えることができます。

SurfacePlot は 3D 専用の新しい mark で、シグネチャは SurfacePlot(x:y:z:_:) の形をしています。クロージャは (Double, Double) -> Double で、各 (x, z) に対して y を計算します(05:19)。

Chart3D {
  SurfacePlot(x: "X", y: "Y", z: "Z") { x, z in
    (sin(5 * x) + sin(5 * z)) / 2
  }
}

ポイント:

  • 3 つの文字列引数 "X" / "Y" / "Z" は軸の識別子であり、chartXScale などの修飾子に対応します。
  • クロージャは単一の Double を返します。Swift Charts は X-Z 平面上で自動的にサンプリングし、連続的な曲面を構築します。
  • 任意の数学式をクロージャに入れるだけで曲面図が得られ、手動でメッシュを構築する必要はありません。

曲面をデータの上に重ねてフィッティングするには、まず MLLinearRegressor で学習し、それを同じ (Double, Double) -> Double のクロージャとしてラップすればよいです(06:19)。

let linearRegression = LinearRegression(
  penguins,
  x: \.flipperLength,
  y: \.weight,
  z: \.beakLength
)

Chart3D {
  ForEach(penguins) { penguin in
    PointMark(
      x: .value("Flipper Length", penguin.flipperLength),
      y: .value("Weight", penguin.weight),
      z: .value("Beak Length", penguin.beakLength)
    )
    .foregroundStyle(by: .value("Species", penguin.species))
  }

  SurfacePlot(x: "Flipper Length", y: "Weight", z: "Beak Length") { flipperLength, beakLength in
    linearRegression(flipperLength, beakLength)
  }
  .foregroundStyle(.gray)
}

ポイント:

  • Chart3D 内部では ForEach と複数の mark を混在させることができ、散布図と曲面を共存させられます。
  • LinearRegressioncallAsFunction によってモデルの予測を通常のクロージャとしてラップし、SurfacePlot に渡せます。
  • 曲面には .foregroundStyle(.gray) を使ってカラーの散布図と対比させ、データを隠さないようにします。

視点と投影は Chart3DPose で制御します(08:09)。azimuth で水平方向の回転、inclination で俯仰を制御します。

@State var pose = Chart3DPose(
  azimuth: .degrees(20),
  inclination: .degrees(7)
)

Chart3D(penguins) { penguin in
  // ...
}
.chart3DPose($pose)
.chart3DCameraProjection(.perspective)

ポイント:

  • Chart3DPose はプリセット値(.default.front)を使うことも、角度をカスタマイズすることもできます。
  • chart3DPose($pose) は Binding を受け取るので、インタラクションに応じて更新できます。
  • chart3DCameraProjection には .orthographic(デフォルト。遠近差がなく、側面から 2D に戻すのに適しています)と .perspective(遠くが小さく近くが大きく、没入感が高まります)の 2 種類があります。

SurfacePlot の着色には、専用のセマンティックスタイルもあります(09:3809:47)。.foregroundStyle(.heightBased) は曲面の高さに応じて色付けし、.foregroundStyle(.normalBased) は曲面の法線方向に応じて色付けします。また、LinearGradientEllipticalGradient を渡すこともできます。


重要ポイント

  • 何をするかChart3D を使って、複数の 2D 散布図を 1 枚の回転可能な図に統合します。

    • なぜ価値があるか:ユーザーはこれまで頭の中で複数の 2D 図を組み合わせる必要がありましたが、3D 回転によってクラスタの実際の形状を直接見ることができます。生物、物理、センサーなど、本来 3 次元であるデータにとって特に価値があります。
    • どう始めるか:既存の ChartChart3D に変更し、PointMarkz を追加し、3 軸すべてに domain と正規化された range(例: -0.5...0.5)を設定します。まずデフォルトの pose を確認し、chart3DPose で典型的なデータセットに最適な視点に微調整します。
  • 何をするかSurfacePlot を使って、モデルの予測を連続的な曲面として可視化します。

    • なぜ価値があるか:線形回帰、ニューラルネットワーク、物理シミュレーションの出力は、いずれも (Double, Double) -> Double の形式をとります。曲面図によって、ユーザーはモデルとデータのずれを直感的に把握できます。
    • どう始めるか:モデルを callAsFunction(_ x: Double, _ z: Double) -> Double としてラップし、SurfacePlot のクロージャに渡します。そして .foregroundStyle(.gray) を使ってカラーの PointMark とともに Chart3D 内に重ねます。
  • 何をするか:visionOS アプリでは 3D グラフを優先的に検討します。

    • なぜ価値があるか:Vision Pro は空間表示に最適なデバイスであり、2D のグラフは空間の中で逆に平面的に見えます。Swift Charts の 3D は visionOS 上でジェスチャーによる回転をネイティブにサポートしています。
    • どう始めるか:visionOS の SwiftUI ビュー内でそのまま Chart3D を使い、投影を .perspective に設定して、遠近関係が深度を認識するのに役立つようにします。
  • 何をするか:使用場面に応じて .orthographic.perspective を使い分けます。

    • なぜ価値があるか:正投影はサイズの横断的な比較に便利です(遠近で大きさが変わらない)。透視投影は深度の手がかりを提供します。混在させるとユーザーが混乱します。
    • どう始めるか:ダッシュボードや、側面から 2D 表示に戻す必要があるグラフには .orthographic を選びます。没入感や空間的な関係を強調したい展示には .perspective を選び、chart3DCameraProjection で切り替えます。

関連セッション

コメント

GitHub Issues · utterances