WWDC22、iOS 16：SwiftUIでChartsフレームワークを使ってチャートを作成する

:::note
※一般公開されているSession/Documentation/Sample Codeページだけを使ってこの記事を執筆しました。
SessionビデオやDocumentationページからの画像を収録しています。
実際のチャートは、Xcode 14でコードを実行することで確認することができます。
:::

新しくリリースされたチャートフレームワークにより、独自のチャートビューを設計したり、サードパーティライブラリを使用する必要がなくなりました。

![charts_type.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/64f75c71-f079-3dbf-b83c-270b333c70a7.jpeg)

最も基本的な機能としては、棒グラフ、ラインポイント（折れ線グラフ）、ポイントチャートを表現することができます。

ここでは、1つのデータ点をデータエントリーと呼ぶことにします。

## データ構造を設計する

各データエントリーを格納するデータ構造を用意する必要があります。最も単純なものは、名前と値を持つ構造体です。

```swift
struct ChartEntry: Identifiable {
    var title: String
    var value: Double
    var color: Color = .green
    var id: String {
        return title + String(value)
    }
}
```

`color`も追加されているので、個々のデータ入力ごとにバー(棒グラフ)/ラインポイント/ポイントの色をカスタマイズすることができます。

また、これを `Identifiable` プロトコルに適合させる必要があります。ListやForEachに対して行うのと同じように。

## `Chart`ビュー要素

最初に、SwiftUIのビューファイル内でチャートフレームワークをインポートしていることを確認してください。`import Charts`

`Chart` ブロックは `List` と非常によく似ています。
すべてのデータエントリを含む配列を `Chart` ビュー要素のパラメータ内に直接置くことができます。

以下のコードでは、 `data`はデータ配列 (データセット。 `[ChartEntry]` として定義されます) で、 `dataPoint` は各データ (`ChartEntry` やその他のカスタム構造体として定義されます) です。
例えば、気温を表示する場合、 `data` はすべての天気の気温のエントリを含む配列であり、 `dataPoint` は1つの気温のエントリである。

```swift
Chart(data) { dataPoint in
    // TODO
}
.frame(height: 200)
```

または、`Chart` ビュー要素内に `ForEach` を配置することもできます。

```swift
Chart {
    ForEach(data) { dataPoint in
        // TODO
    }
}
.frame(height: 200)
```

ForEach`ブロック内では、各データエントリに表示するビューを作成します。
例えば、棒グラフを表示したい場合は、各データエントリーに棒(バー)のビュー(BarMark)を作成します。

## 棒グラフ

ビュー要素 `BarMark` を使用し、X軸にタイトル、Y軸に値を表示します。

```swift
BarMark(
    x: .value("タイトル", dataPoint.title),
    y: .value("値", dataPoint.value)
)
.foregroundStyle(dataPoint.color)
```

上記のコードをChartまたはForEachブロック内に配置します。

```swift
let data: [ChartEntry] = [
    .init(title: "A", value: 5),
    .init(title: "B", value: 10),
    .init(title: "C", value: 8)
]

Chart(data) { dataPoint in
    BarMark(
        x: .value("タイトル", dataPoint.title),
        y: .value("値", dataPoint.value)
    )
    .foregroundStyle(dataPoint.color)
}
.frame(height: 200)
```

そして、棒グラフが表示されます。

![CreatingYourFirstChart-2@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/dda5f072-8272-c1bb-50fe-e5a4288fa4da.png)

### 同じカテゴリーに複数のバーを追加する。

Chartビューでは、X軸のタイトル（名前）でカテゴリを識別します。
同じX軸の名前を持つ要素が複数ある場合、Chartビューは既存のバーの上にバーを表示します。

```swift
let data_test: [ChartEntry] = [
    .init(title: "A", value: 5),
    .init(title: "A", value: 10, color: .blue),
    .init(title: "B", value: 10),
    .init(title: "C", value: 8)
]
Chart {
    ForEach(data_test) { dataPoint in
        BarMark(
            x: .value("Category", dataPoint.title),
            y: .value("Value", dataPoint.value)
        )
        .foregroundStyle(dataPoint.color)
    }
}
.frame(height: 200)
```

上記のコード例では、X軸の名前を "A"とし、色を青としたデータエントリーを追加定義しているのがわかります。これで、チャートは次の画像のようになります。

![bar-chart-02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/e7dcde8f-50bc-0c5f-9ed9-61fc6b1bc782.jpeg)


この場合、同じ名前 "A "を持つ2つのデータが統合され、同じ縦線に表示されています。

## x軸とy軸を反転させる

x軸にラベル、y軸に値を表示する代わりに、x軸に値を表示することもできます（横線グラフ）。

![horizontal_chart.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/42a9f042-70eb-af5b-2ff5-c944fd356a30.jpeg)

```swift
let data: [ChartEntry] = [
    .init(title: "A", value: 5),
    .init(title: "B", value: 10),
    .init(title: "C", value: 8)
]

Chart(data) { dataPoint in
    BarMark(
        x: .value("Value", dataPoint.value),
        y: .value("Category", dataPoint.title)
    )
    .foregroundStyle(dataPoint.color)
}
.frame(height: 200)
```

xパラメータに値、yパラメータにタイトルを入れることができます。

## 表示されているチャートを更新する

チャートに入力するデータセットに `@State` 変数を使うこともできる。そうすれば、データを更新したり、別のデータセットを表示したりすることができる。

```swift
struct ContentView: View {
    
    static let demoData1: [ChartEntry] = [
        .init(title: "A", value: 5),
        .init(title: "B", value: 10),
        .init(title: "C", value: 8)
    ]
    static let demoData2: [ChartEntry] = [
        .init(title: "A", value: 5),
        .init(title: "A", value: 10, color: .blue),
        .init(title: "B", value: 10),
        .init(title: "C", value: 8)
    ]
    
    @State var data: [ChartEntry] = [
        .init(title: "A", value: 5),
        .init(title: "B", value: 10),
        .init(title: "C", value: 8)
    ]
    @State private var dataSetSelection: Int = 0
    
    var body: some View {
        
        Form {
            
            Picker("Dataset selection", selection: $dataSetSelection) {
                Text("Dataset 1")
                    .tag(0)
                Text("Dataset 2")
                    .tag(1)
            }
            .onChange(of: dataSetSelection) { newValue in
                withAnimation {
                    if newValue == 0 {
                        self.data = ContentView.demoData1
                    } else if newValue == 1 {
                        self.data = ContentView.demoData2
                    }
                }
            }
            
            Chart {
                ForEach(data) { dataPoint in
                    BarMark(
                        x: .value("Value", dataPoint.value),
                        y: .value("Category", dataPoint.title)
                    )
                    .foregroundStyle(dataPoint.color)
                }
            }
            .frame(height: 200)
            
        }
        
    }
    
}
```

データが更新されるとき、`withAnimation`ブロック内にコードを置くと、チャートはアニメーションで更新されます。

## ラインチャート(折れ線グラフ)

![line_chart_video.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/7c7b4daf-55ba-3b15-e459-489a54cd6edd.jpeg)

BarMark`を`LineMark`に変更するだけで、ラインチャート(折れ線グラフ)が表示されるようになります。

```swift
Chart {
    ForEach(data) { dataPoint in
        LineMark(
            x: .value("Category", dataPoint.title),
            y: .value("Value", dataPoint.value)
        )
        .foregroundStyle(dataPoint.color)
    }
}
.frame(height: 200)
```

注：折れ線グラフの場合、値は通常Y軸に、ラベルは通常X軸に表示されます。

## ポイントチャート

`BarMark` を `PointMark` に変更すると、ポイントチャートが得られます。

![point_chart.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/a17f7eea-64b0-27cb-7648-594a8adc52e4.jpeg)

```swift
Chart {
    ForEach(data) { dataPoint in
        PointMark(
            x: .value("Category", dataPoint.title),
            y: .value("Value", dataPoint.value)
        )
        .foregroundStyle(dataPoint.color)
    }
}
.frame(height: 200)
```

注：ポイントチャートの場合、値は通常Y軸に、ラベルは通常X軸に表示されます。

## 点で構成される折れ線グラフを表示する

![line_point.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/2da8ee71-87b1-deb9-e178-dfea472418c9.jpeg)

上で説明したように、同じX軸のラベルを持つ複数のデータポイントがある場合、SwiftUIはそれらを結合します。そこで、これを利用して、ポイントを持つ折れ線グラフを表示することができます。

基本的に、ForEachループ内では、LineMark（折れ線グラフに使用）とPointMark（点線グラフに使用）の両方を返すことになります。

```swift
Chart {
    ForEach(Charts_PointLine.demoData1) { dataPoint in
        LineMark(
            x: .value("Category", dataPoint.title),
            y: .value("Value", dataPoint.value)
        )
        .foregroundStyle(dataPoint.color)
        PointMark(
            x: .value("Category", dataPoint.title),
            y: .value("Value", dataPoint.value)
        )
    }
}
.frame(height: 200)
```

## 1つのグラフに複数のデータセットを表示する

1つのグラフの中に複数のデータセットを表示することもできます。

![point_chart.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/cc7bebe9-5709-8ea3-b231-b4b3c995d9b3.jpeg)

まず、データセット（データの集合体）を表す構造体を新規に作成します。

```swift
struct DataCollection: Identifiable {
    var collectionName: String
    var data: [ChartEntry]
    var id: String { return collectionName }
}
```

上記のデータセットオブジェクトには、データセットの名前と、データエントリーが含まれます。

そして、データエントリーを持つデータセットを作成することができます。

```swift
static let demoData1: [ChartEntry] = [
    .init(title: "Morning", value: 20),
    .init(title: "Noon", value: 27),
    .init(title: "Evening", value: 21)
]
static let demoData2: [ChartEntry] = [
    .init(title: "Morning", value: 13),
    .init(title: "Noon", value: 14),
    .init(title: "Evening", value: 8)
]

@State var dataCollections: [DataCollection] = [
    .init(collectionName: "Kyoto", data: demoData1),
    .init(collectionName: "Hokkaido", data: demoData2)
]
```

各データセットをループするために、すべてのデータセットを含む配列(dataCollections)を `Chart` のパラメータに渡します。
`Chart`のブロックの中で、一つのデータセット内のすべてのデータエントリー(collection.data)をループするために、 `ForEach` を追加します。

```swift
Chart(dataCollections) { collection in
    ForEach(collection.data) { dataPoint in
        PointMark(
            x: .value("Category", dataPoint.title),
            y: .value("Value", dataPoint.value)
        )
        .foregroundStyle(by: .value("City", collection.collectionName))
    }
}
.frame(height: 200)
```

チャートに都市の名前を表示するために、`.foregroundStyle(by: .value("City", collection.collectionName))` を使って、チャートに都市の名前を表示しました。

SwiftUIは自動的に各データセットに異なる色を選択します。

## 水平線・垂直線を表示する (RuleMark)

チャートに水平線または垂直線を表示することができます。
例えば、チャート内の全データの平均値を示すのに使用できます。

水平線: `RuleMark(y: <#T##PlottableValue<Plottable>#>)`
垂直線: `RuleMark(s: <#T##PlottableValue<Plottable>#>)`

![chart_rulemark.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/8ca69ef4-7782-7e41-d02a-ebfee2f9cf59.jpeg)

## 高度なチャートタイプ

他にもいくつかの高度なチャートタイプがあります。
例えば、`AreaMark`を使えば、チャート上のある領域をマークすることができます。
例えば、温度範囲をマークすることができます。

```swift
struct ChartEntry_minMax: Identifiable {
    var title: String
    var minY: Int
    var maxY: Int
    var averageY: Int {
        return (minY + maxY) / 2
    }
    var id: String { return title }
}

struct Charts_AreaMark: View {
    
    @State var data: [ChartEntry_minMax] = [
        .init(title: "Today", minY: 15, maxY: 27),
        .init(title: "Tomorrow", minY: 17, maxY: 28),
        .init(title: "Saturday", minY: 18, maxY: 24),
        .init(title: "Sunday", minY: 16, maxY: 25),
        .init(title: "Monday", minY: 14, maxY: 26),
        .init(title: "Tuesday", minY: 17, maxY: 20),
        .init(title: "Wednesday", minY: 17, maxY: 23)
    ]
    
    var body: some View {
        
        Form {
            
            Chart {
                ForEach(data) { dataPoint in
                    AreaMark(
                        x: .value("Category", dataPoint.title),
                        yStart: .value("Lowest temperature", dataPoint.minY),
                        yEnd: .value("Highest temperature", dataPoint.maxY)
                    )
                    .opacity(0.3)
                    LineMark(
                        x: .value("Category", dataPoint.title),
                        y: .value("Average", dataPoint.averageY)
                    )
                }
            }
            .frame(height: 200)
            
        }
        .navigationTitle("Temperature")
        
    }
    
}
```

![area_chart_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/8616b5d9-aa28-4367-0ac1-8d7a84531f4c.jpeg)

他の種類のチャートについて知りたい方は、WWDCのビデオを見てください。

https://developer.apple.com/wwdc22/10137 

https://developer.apple.com/wwdc22/10136 

https://developer.apple.com/wwdc22/110342 

https://developer.apple.com/wwdc22/110340

--- 

Enjoy the WWDC!

---


:thumbsup: お読みいただきありがとうございました。

[☺️ Twitter @MszPro](https://twitter.com/MszPro)
[🐘 Mastodon @me@mszpro.com](https://sns.mszpro.com/@me)

:sunny: <img width="150" src="https://cdn.mszmagic.com/static-web-content/AppClipImage_small.png">

---
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/4b58c440-9807-6839-caf7-0a311c7610e0.png">

Written by MszPro~

---

# 関連記事

・[UICollectionViewの行セル、ヘッダー、フッター、またはUITableView内でSwiftUIビューを使用（iOS 16, UIHostingConfiguration）](https://qiita.com/MaShunzhe/items/3d98fa897aa1bcffc74b#github-repository)

・[iPhone 14 ProのDynamic Islandにウィジェットを追加し、Live Activitiesを開始する（iOS16.1以降）](https://qiita.com/MaShunzhe/items/6cc8d9d204f40cbee19c)

・[iOS 16：秘密値の保存、FaceID認証に基づく個人情報の表示/非表示（LARight）](https://qiita.com/MaShunzhe/items/07c1e65277a5008106db)

・[iOS16 MapKitの新機能 : 地図から場所を選ぶ、通りを見回す、検索補完](https://qiita.com/MaShunzhe/items/693e6302876707d5684d)

・[SwiftUIアプリでバックグラウンドタスクの実行(ネットワーク、プッシュ通知) (BackgroundTasks, URLSession)](https://qiita.com/MaShunzhe/items/4e0d1032748fe4f398de)

・[WWDC22、iOS16：iOSアプリに画像からテキストを選択する機能を追加（VisionKit）](https://qiita.com/MaShunzhe/items/ed3985d05975bf35292d)

・[WWDC22、iOS16：数行のコードで作成できるSwiftUIの新機能（26本）](https://qiita.com/MaShunzhe/items/0ed36d8a6de7c56b1d34)

・[WWDC22、iOS 16：SwiftUIでChartsフレームワークを使ってチャートを作成する](https://qiita.com/MaShunzhe/items/cae62af8b07f4f474fd8)

・[WWDC22, iOS 16: WeatherKitで気象データを取得](https://qiita.com/MaShunzhe/items/dce45ccdd05c1f70cf54)

・[WWDC 2022の基調講演のまとめ記事](https://qiita.com/MaShunzhe/items/037580d2ed51a5a6e9d9)

---

Github repo

このGitHubリポジトリには、この記事で参照したコードが含まれています。

https://github.com/mszpro/SwiftUI_Chart_Demo

---