สร้างกราฟ 3 มิติใน iOS 26 ด้วย Chart3D และ SurfacePlot

เรียนรู้การสร้างกราฟ 3 มิติแบบ interactive ด้วย Chart3D และ SurfacePlot ใน iOS 26 พร้อมโค้ดตัวอย่างครบถ้วน ตั้งแต่ PointMark, การควบคุมมุมกล้อง ไปจนถึง Projection บนทุกแพลตฟอร์ม Apple

ทำไม Chart3D ถึงเปลี่ยนเกมการแสดงผลข้อมูลบน iOS

ถ้าคุณเคยใช้ Swift Charts ตั้งแต่ iOS 16 คุณก็รู้ดีว่ามันสร้างกราฟ 2 มิติได้สวยงามมาก แต่พอต้องการแสดงข้อมูล 3 มิติล่ะ? ก็ต้องไปวุ่นวายกับ SceneKit หรือไลบรารีของบุคคลที่สามที่ทั้งซับซ้อนและยากต่อการบำรุงรักษา

พอมาถึง iOS 26 ทุกอย่างเปลี่ยนไป Apple ปล่อย Chart3D ออกมา — เป็น API ที่ให้คุณสร้างกราฟ 3 มิติแบบ interactive ได้โดยตรงใน SwiftUI โดยไม่ต้องแตะ SceneKit เลยแม้แต่บรรทัดเดียว

พูดตรงๆ ว่าตอนลองใช้ครั้งแรก รู้สึกทึ่งมากที่ syntax แทบจะเหมือนกับ Chart 2D ที่คุ้นเคย เอาล่ะ มาดูกันว่ามันทำอะไรได้บ้าง ตั้งแต่พื้นฐานจนถึงเทคนิคขั้นสูง พร้อมโค้ดตัวอย่างที่ใช้งานได้จริง

สิ่งที่คุณต้องเตรียม

  • Xcode 26 ขึ้นไป
  • iOS 26, macOS 26, visionOS 26, watchOS 26 หรือ iPadOS 26 เป็น deployment target
  • ความรู้พื้นฐานเกี่ยวกับ SwiftUI และ Swift Charts (กราฟ 2 มิติ)

ทำความรู้จักกับ Chart3D และ Component หลัก

Chart3D ถูกออกแบบมาให้ใช้งานคล้ายกับ Chart ที่คุณคุ้นเคยอยู่แล้ว เพียงแค่เปลี่ยนจาก Chart เป็น Chart3D แล้วเพิ่มแกน Z เข้าไป แค่นั้นเอง

Component หลักของ Chart3D มีดังนี้:

  • Chart3D — container หลักสำหรับแสดงผลกราฟ 3 มิติทั้งหมด
  • SurfacePlot — พล็อตพื้นผิว 3 มิติจากฟังก์ชันทางคณิตศาสตร์ (ตัวนี้เจ๋งมาก)
  • PointMark (3D) — จุดข้อมูลที่รองรับแกน X, Y และ Z
  • RuleMark (3D) — เส้นอ้างอิงในพื้นที่ 3 มิติ
  • RectangleMark (3D) — สี่เหลี่ยมที่รองรับแกน Z

เริ่มต้นกับ PointMark ในกราฟ 3 มิติ

วิธีที่ง่ายที่สุดในการเริ่มใช้ Chart3D คือเอา scatter plot 2 มิติที่มีอยู่แล้วมาแปลงเป็น 3 มิติ สมมติว่าเรามีข้อมูลชุดดอกไอริส (Iris dataset) ที่ใช้กันทั่วไปในงาน Data Science:

import SwiftUI
import Charts

struct IrisData: Identifiable {
    let id = UUID()
    let petalLength: Double
    let petalWidth: Double
    let sepalLength: Double
    let species: String
}

let irisData: [IrisData] = [
    IrisData(petalLength: 1.4, petalWidth: 0.2, sepalLength: 5.1, species: "Setosa"),
    IrisData(petalLength: 4.7, petalWidth: 1.4, sepalLength: 7.0, species: "Versicolor"),
    IrisData(petalLength: 6.0, petalWidth: 2.5, sepalLength: 7.6, species: "Virginica"),
    // เพิ่มข้อมูลตามต้องการ
]

struct Scatter3DView: View {
    var body: some View {
        Chart3D(irisData) { iris in
            PointMark(
                x: .value("ความยาวกลีบดอก", iris.petalLength),
                y: .value("ความกว้างกลีบดอก", iris.petalWidth),
                z: .value("ความยาวกลีบเลี้ยง", iris.sepalLength)
            )
            .foregroundStyle(by: .value("สายพันธุ์", iris.species))
        }
        .frame(width: 400, height: 350)
    }
}

สังเกตไหมว่าโค้ดเกือบจะเหมือนกับ Chart 2 มิติเป๊ะเลย? แค่เปลี่ยนเป็น Chart3D แล้วเพิ่มพารามิเตอร์ z เข้าไป จากนั้น .foregroundStyle(by:) ก็จัดการลงสีตามหมวดหมู่ให้อัตโนมัติ ง่ายมาก

ปรับแต่ง Symbol และขนาดของ PointMark

อยากให้จุดข้อมูลดูเท่ขึ้น? ใน Chart3D คุณเปลี่ยนรูปทรงได้หลายแบบเลย:

PointMark(
    x: .value("X", item.x),
    y: .value("Y", item.y),
    z: .value("Z", item.z)
)
.symbol(.cube)       // รูปทรง: .sphere, .cylinder, .cone, .cube
.symbolSize(0.05)    // ปรับขนาด

รูปทรงที่ใช้ได้มี .sphere (ทรงกลม), .cube (ลูกบาศก์), .cylinder (ทรงกระบอก) และ .cone (ทรงกรวย) ซึ่งช่วยแยกแยะกลุ่มข้อมูลได้ชัดเจนยิ่งขึ้น ส่วนตัวชอบใช้ .sphere มากที่สุด เพราะดูเป็นธรรมชาติกว่า

สร้างพื้นผิว 3 มิติด้วย SurfacePlot

มาถึงฟีเจอร์เด่นกันแล้ว SurfacePlot ช่วยให้คุณแสดงผลฟังก์ชันทางคณิตศาสตร์เป็นพื้นผิว 3 มิติได้อย่างสวยงาม API ของมันคล้ายกับ LinePlot คือรับ closure ที่รับค่า Double 2 ตัว (x, z) แล้ว return ค่า Double 1 ตัว (y):

struct SurfacePlotView: View {
    var body: some View {
        Chart3D {
            SurfacePlot(
                x: "แกน X",
                y: "ความสูง",
                z: "แกน Z"
            ) { x, z in
                sin(x) * cos(z)
            }
            .foregroundStyle(.heightBased)
        }
        .frame(width: 500, height: 400)
    }
}

โค้ดแค่ไม่กี่บรรทัดนี้จะสร้างพื้นผิวรูปคลื่นจากฟังก์ชัน sin(x) * cos(z) ขึ้นมาให้เลย โดย .foregroundStyle(.heightBased) จะลงสีตามความสูงของพื้นผิวโดยอัตโนมัติ ผลลัพธ์สวยมากจริงๆ

ตัวอย่างจริง: แสดงผลสูตรฟิสิกส์ ระยะทาง = ความเร็ว × เวลา

struct PhysicsChartView: View {
    var body: some View {
        Chart3D {
            SurfacePlot(
                x: "ความเร็ว (m/s)",
                y: "ระยะทาง (m)",
                z: "เวลา (s)"
            ) { speed, time in
                speed * time
            }
            .foregroundStyle(.heightBased)
        }
        .frame(width: 500, height: 400)
    }
}

ตัวอย่างนี้โชว์ให้เห็นว่า SurfacePlot เหมาะมากสำหรับแสดงผลโมเดลทางคณิตศาสตร์ สูตรฟิสิกส์ หรือแม้แต่ผลลัพธ์จาก ML regression ถ้าคุณทำแอปด้านการศึกษา ฟีเจอร์นี้คือของขวัญชิ้นใหญ่เลย

ปรับแต่ง foregroundStyle สำหรับ SurfacePlot

SurfacePlot รองรับการลงสีแบบพิเศษ 2 แบบ:

  • .heightBased — ลงสีตามความสูง (ค่า Y) ของพื้นผิวที่จุดนั้น
  • .normalBased — ลงสีตามมุมเอียงของพื้นผิวที่จุดนั้น (ให้อารมณ์คล้ายแสงตกกระทบ)

แต่ถ้าอยากกำหนดสีเอง ก็ใช้ gradient แบบ custom ได้เลย:

SurfacePlot(x: "X", y: "Y", z: "Z") { x, z in
    sin(x) * cos(z)
}
.foregroundStyle(
    LinearGradient(
        colors: [.blue, .cyan, .green, .yellow, .red],
        startPoint: .bottom,
        endPoint: .top
    )
)

ควบคุมมุมกล้องด้วย Chart3DPose

จุดเด่นอีกอย่างของ Chart3D คือผู้ใช้สามารถหมุนกราฟด้วย gesture ได้เลยโดยอัตโนมัติ แต่ถ้าคุณอยากกำหนดมุมมองเริ่มต้นเอง ก็ทำได้ด้วย Chart3DPose:

struct ControlledPoseView: View {
    @State private var pose = Chart3DPose(
        azimuth: .degrees(30),     // หมุนซ้าย-ขวา
        inclination: .degrees(15)  // เอียงขึ้น-ลง
    )

    var body: some View {
        Chart3D(irisData) { iris in
            PointMark(
                x: .value("X", iris.petalLength),
                y: .value("Y", iris.petalWidth),
                z: .value("Z", iris.sepalLength)
            )
        }
        .chart3DPose($pose)
    }
}

เนื่องจาก pose เป็น @State binding ผู้ใช้ก็ยังหมุนกราฟด้วย gesture ได้ปกติ แถมคุณยังสามารถรีเซ็ตมุมมองหรือสร้าง animation ได้อีกด้วย

สร้างกราฟหมุนอัตโนมัติด้วย Timer

อันนี้สนุกมาก ลองสร้างกราฟที่หมุนรอบตัวเองอัตโนมัติกัน:

import Spatial

struct RotatingChartView: View {
    @State private var pose = Chart3DPose(
        azimuth: .degrees(0),
        inclination: .degrees(20)
    )
    @State private var azimuth: Angle2D = .degrees(0)

    private let timer = Timer.publish(
        every: 0.03,
        on: .main,
        in: .common
    ).autoconnect()

    var body: some View {
        Chart3D {
            SurfacePlot(x: "X", y: "Y", z: "Z") { x, z in
                sin(x) * cos(z)
            }
            .foregroundStyle(.heightBased)
        }
        .chart3DPose($pose)
        .onReceive(timer) { _ in
            azimuth += .degrees(1)
            pose = Chart3DPose(
                azimuth: azimuth,
                inclination: .degrees(20)
            )
        }
    }
}

กราฟจะหมุนรอบตัวเองไปเรื่อยๆ ดูดีมากถ้าเอาไปใส่ในหน้า demo หรือ onboarding screen ของแอป

กำหนดมาตราส่วนแกนและ Projection

ปรับ Scale ของแต่ละแกน

เหมือนกับ Chart 2D คุณกำหนด domain และ range ของแต่ละแกนได้ รวมถึงแกน Z ที่เพิ่มเข้ามาใหม่:

Chart3D(data) { item in
    PointMark(
        x: .value("X", item.x),
        y: .value("Y", item.y),
        z: .value("Z", item.z)
    )
}
.chartXScale(domain: 0...10, range: -1.5...1.5)
.chartYScale(domain: 0...5, range: -0.5...0.5)
.chartZScale(domain: 0...10, range: -0.5...0.5)

เปลี่ยน Projection

โดยค่าเริ่มต้น Chart3D ใช้ Orthographic Projection ซึ่งแสดงวัตถุในขนาดเท่ากันไม่ว่าจะอยู่ใกล้หรือไกล หากต้องการให้รู้สึกมีความลึกเหมือนมองด้วยตาจริง ให้เปลี่ยนเป็น Perspective Projection:

.chart3DCameraProjection(.perspective)

Perspective projection เหมาะกับกราฟที่ต้องการเน้นความลึก โดยเฉพาะบน visionOS ที่ผู้ใช้มองเห็นในพื้นที่ 3 มิติจริงๆ ถ้าแอปของคุณรองรับ Apple Vision Pro แนะนำให้ลองใช้โหมดนี้ดู

รองรับหลายแพลตฟอร์ม: iOS, macOS, visionOS, watchOS

ข่าวดีคือ Chart3D ทำงานได้บนทุกแพลตฟอร์มของ Apple ที่รันเวอร์ชัน 26 ขึ้นไป แต่แต่ละแพลตฟอร์มมีข้อดีเฉพาะตัว:

  • iOS / iPadOS 26 — รองรับ gesture หมุนด้วยนิ้วบนหน้าจอ
  • macOS 26 — หมุนด้วย trackpad หรือเมาส์ เหมาะกับแอปวิเคราะห์ข้อมูล
  • visionOS 26 — แสดงผลในพื้นที่ 3 มิติจริงด้วย stereoscopic display เหมาะสำหรับ spatial analytics
  • watchOS 26 — แสดงกราฟขนาดเล็กบนหน้าจอนาฬิกา (จอเล็กแต่ก็ใช้ได้นะ)

สำหรับการรองรับ backward compatibility อย่าลืมใช้ @available guard:

@available(iOS 26, macOS 26, visionOS 26, watchOS 26, *)
struct My3DChartView: View {
    var body: some View {
        Chart3D {
            // ...
        }
    }
}

กรณีใช้งานจริงที่เหมาะกับ Chart3D

แล้ว Chart3D เหมาะกับงานแบบไหน? จากที่ลองใช้มา มันเหมาะเป็นพิเศษเมื่อ:

  • ข้อมูลเป็น 3 มิติโดยธรรมชาติ — เช่น ตำแหน่งพิกัดในพื้นที่ 3 มิติ, ข้อมูลเซ็นเซอร์ accelerometer
  • รูปทรงของข้อมูลสำคัญกว่าค่าตัวเลข — เช่น การจัดกลุ่มข้อมูล (clustering), การแบ่งประเภท (classification)
  • แสดงผลฟังก์ชันทางคณิตศาสตร์ — เช่น สูตรฟิสิกส์, ผลลัพธ์ Machine Learning regression
  • แอปด้านวิทยาศาสตร์หรือการศึกษา — เช่น แสดงผลโมเลกุลเคมี, ภูมิประเทศ (topography)
  • visionOS spatial analytics — ใช้ประโยชน์จากการแสดงผล 3 มิติจริงของ Apple Vision Pro

เมื่อไหร่ที่ไม่ควรใช้ Chart3D

อันนี้สำคัญ — Apple เองก็แนะนำว่ากราฟ 3 มิติเหมาะเมื่อ interaction ช่วยเพิ่มความเข้าใจข้อมูล ถ้าข้อมูลเข้าใจได้ดีพอแล้วในรูปแบบ 2 มิติ ก็ไม่จำเป็นต้องใช้ 3 มิติ เพราะมันอาจทำให้อ่านค่าตัวเลขที่แม่นยำได้ยากขึ้น อย่ายัดกราฟ 3D ไปทุกที่เพียงเพราะมันดูเท่

ตัวอย่างครบวงจร: แอปแสดงผลข้อมูลเซ็นเซอร์ 3 มิติ

มาถึงตัวอย่างที่รวมทุกเทคนิคเข้าด้วยกัน — scatter plot พร้อม symbol แบบกำหนดเอง, การควบคุมกล้อง และปุ่มรีเซ็ตมุมมอง:

import SwiftUI
import Charts
import Spatial

struct SensorData: Identifiable {
    let id = UUID()
    let x: Double
    let y: Double
    let z: Double
    let category: String
}

@available(iOS 26, *)
struct FullExample3DView: View {
    @State private var pose = Chart3DPose(
        azimuth: .degrees(45),
        inclination: .degrees(25)
    )

    let sensorReadings: [SensorData] = [
        SensorData(x: 1.2, y: 3.4, z: 2.1, category: "ปกติ"),
        SensorData(x: 5.6, y: 1.8, z: 4.3, category: "เตือน"),
        SensorData(x: 3.3, y: 4.2, z: 1.7, category: "ปกติ"),
        SensorData(x: 7.1, y: 6.5, z: 5.9, category: "วิกฤต"),
        SensorData(x: 2.8, y: 2.1, z: 3.5, category: "ปกติ"),
        SensorData(x: 6.4, y: 5.0, z: 6.2, category: "เตือน"),
    ]

    var body: some View {
        VStack {
            Chart3D(sensorReadings) { reading in
                PointMark(
                    x: .value("แกน X", reading.x),
                    y: .value("แกน Y", reading.y),
                    z: .value("แกน Z", reading.z)
                )
                .foregroundStyle(by: .value("สถานะ", reading.category))
                .symbol(.sphere)
                .symbolSize(0.04)
            }
            .chart3DPose($pose)
            .chart3DCameraProjection(.perspective)
            .chartXScale(domain: 0...8)
            .chartYScale(domain: 0...8)
            .chartZScale(domain: 0...8)
            .frame(height: 400)

            Button("รีเซ็ตมุมมอง") {
                withAnimation(.easeInOut(duration: 0.5)) {
                    pose = Chart3DPose(
                        azimuth: .degrees(45),
                        inclination: .degrees(25)
                    )
                }
            }
            .buttonStyle(.borderedProminent)
        }
        .padding()
    }
}

ตัวอย่างนี้แสดงข้อมูลเซ็นเซอร์ 3 มิติ แบ่งสีตามสถานะ (ปกติ, เตือน, วิกฤต) ใช้ perspective projection เพื่อให้ดูมีความลึก แถมมีปุ่มรีเซ็ตมุมมองพร้อม animation ให้ด้วย ลองเอาไปปรับใช้กับข้อมูลจริงของคุณได้เลย

คำถามที่พบบ่อย (FAQ)

Chart3D ต่างจาก SceneKit อย่างไร?

Chart3D เป็น API เฉพาะสำหรับแสดงผลข้อมูลในรูปแบบกราฟ 3 มิติ ใช้ syntax เดียวกับ Swift Charts ที่คุ้นเคย ส่วน SceneKit เป็นเอนจิน 3D เต็มรูปแบบสำหรับเกมและแอนิเมชัน ถ้าคุณแค่อยากแสดงข้อมูลเชิงสถิติ Chart3D เหมาะกว่ามาก เพราะมี axis, label และ legend มาให้ในตัวเลย

Chart3D รองรับ iOS เวอร์ชันเก่ากว่า 26 ได้ไหม?

ไม่ได้ Chart3D ต้องใช้ iOS 26 ขึ้นไปเท่านั้น ถ้าต้องรองรับเวอร์ชันเก่า ก็ใช้ @available(iOS 26, *) guard แล้วทำ fallback เป็นกราฟ 2 มิติสำหรับอุปกรณ์รุ่นก่อนหน้า

ใช้ Chart3D กับข้อมูลจาก Core Data หรือ SwiftData ได้ไหม?

ได้เลย แค่ query ข้อมูลจาก Core Data หรือ SwiftData แล้วแปลงเป็น array ของ struct ที่ conform Identifiable ก็ส่งเข้า Chart3D ได้เลย เหมือนที่ทำกับ Chart 2 มิติปกติ

SurfacePlot รองรับข้อมูลแบบ discrete (ไม่ใช่ฟังก์ชัน) ไหม?

SurfacePlot ออกแบบมาสำหรับฟังก์ชันต่อเนื่อง (continuous function) เป็นหลัก ถ้ามีข้อมูลแบบ discrete ควรใช้ PointMark ใน Chart3D แทน เหมาะกว่าสำหรับข้อมูลจุดที่ไม่ต่อเนื่อง

Chart3D ทำงานบน Apple Vision Pro ได้ดีแค่ไหน?

ต้องบอกว่าทำงานได้ดีมาก Chart3D ใช้ประโยชน์จาก stereoscopic display ของ Apple Vision Pro ได้เต็มที่ ทำให้ผู้ใช้รู้สึกเหมือนกราฟลอยอยู่ในอากาศจริงๆ รองรับ gesture สำหรับหมุนและซูมในพื้นที่ 3 มิติ เหมาะอย่างยิ่งสำหรับแอปวิเคราะห์ข้อมูลเชิงพื้นที่ ถ้ามี Vision Pro อยู่ต้องลอง

เกี่ยวกับผู้เขียน Editorial Team

Our team of expert writers and editors.