Swift Charts で箱ひげ図を描く

# 概要
Swift Charts には6つの mark があります。Mark とは、図を構成する単位のことです。
WWDC では、これらの mark を組み合わせることで様々なグラフを作ることができると言っていました。

![Screen Shot 2022-06-30 at 9.53.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/747207/a216c871-61d1-1650-e0bc-f5c3a79c4ef4.png)
（[WWDC 2022 Swift Charts: Raise the bar](https://developer.apple.com/wwdc22/10137)）

今回は、WWDC の動画で例に挙がっていた箱ひげ図を書いてみます。スクリーンショットは載せられないのですが、以下の箱ひげ図とほぼ同じものができます。

![Screen Shot 2022-06-30 at 9.54.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/747207/57cce2f3-7ad0-fc7c-0070-cb9ee6afd672.png)
（[WWDC 2022 Swift Charts: Raise the bar](https://developer.apple.com/wwdc22/10137)）

# コード
Swift Charts を使うには `Charts` パッケージを import します。
```swift
import Charts
```

まず、データの準備です。今回は箱ひげ図用の `BoxPlotData` と外れ値用の `PointData` という2つの struct を定義しました。
```swift
struct PointData: Identifiable {
    var value: Double
    var id = UUID()
}

struct BoxplotData: Identifiable {
    var name: String
    var maximum: Double
    var thirdQuantile: Double
    var median: Double
    var firstQuantile: Double
    var minimum: Double
    var outliers: [PointData]
    var id = UUID()
}

let type1Data: [BoxplotData] = [
    .init(name: "A", maximum: 180, thirdQuantile: 160, median: 150, firstQuantile: 140, minimum: 90, outliers: [.init(value: 190), .init(value: 195), .init(value: 210), .init(value: 80), .init(value: 78)]),
    .init(name: "B", maximum: 180, thirdQuantile: 160, median: 150, firstQuantile: 140, minimum: 90, outliers: [.init(value: 190), .init(value: 195), .init(value: 210), .init(value: 80), .init(value: 78)])
]

let type2Data: [BoxplotData] = [
    .init(name: "A", maximum: 160, thirdQuantile: 140, median: 130, firstQuantile: 120, minimum: 60, outliers: [.init(value: 170), .init(value: 175), .init(value: 180), .init(value: 60), .init(value: 58)]),
    .init(name: "B", maximum: 160, thirdQuantile: 140, median: 130, firstQuantile: 120, minimum: 60, outliers: [.init(value: 170), .init(value: 175), .init(value: 180), .init(value: 60), .init(value: 58)])
]

let allData: [(type: String, data: [BoxplotData])] = [
    (type: "Type 1", data: type1Data),
    (type: "Type 2", data: type2Data)
]
```

では、実際に書いていきます。
```swift
struct ContentView: View {
    var body: some View {
        Chart {
            ForEach(allData, id: \.type) { typeData in
                ForEach(typeData.data, id: \.name) { dat in

                    // 箱
                    BarMark(
                        x: .value("Name", dat.name),
                        yStart: .value("First Quantile", dat.firstQuantile),
                        yEnd: .value("Third Quantile", dat.thirdQuantile)
                    )

                    // 縦線
                    RuleMark(
                        x: .value("Name", dat.name),
                        yStart: .value("Minimum", dat.minimum),
                        yEnd: .value("Maximum", dat.maximum)
                    )

                    // 最小値の横線
                    RectangleMark(
                        x: .value("Name", dat.name),
                        y: .value("Minimum", dat.minimum),
                        width: .ratio(0.2),
                        height: 2
                    )

                    // 最大値の横線
                    RectangleMark(
                        x: .value("Name", dat.name),
                        y: .value("Maximum", dat.maximum),
                        width: .ratio(0.2),
                        height: 2
                    )

                    // 中央値の横線
                    RectangleMark(
                        x: .value("Name", dat.name),
                        y: .value("Median", dat.median),
                        height: 2
                    )
                    .foregroundStyle(.black)

                    // 外れ値
                    ForEach(dat.outliers) { outlier in
                        PointMark(
                            x: .value("Name", dat.name),
                            y: .value("Value", outlier.value)
                        )
                        .opacity(0.6)
                    }
                }
                .foregroundStyle(by: .value("Type", typeData.type))
                .position(by: .value("Type", typeData.type))
            }
        }
        .padding(20)
    }
}
```

箱のプロットに `BarMark` を使っていますが、これは `RectangleMark` に置き換えても同じ結果になります：
```swift
                    RectangleMark(
                        x: .value("Name", dat.name),
                        yStart: .value("First Quantile", dat.firstQuantile),
                        yEnd: .value("Third Quantile", dat.thirdQuantile)
                    )
```

# まとめ
今回は Bar、Rule、Rectangle、Point の4つの mark を使って箱ひげ図が作れることが分かりました。このように、複数の mark を使って様々なグラフを作ることができそうです。

# 参考
- https://developer.apple.com/wwdc22/10137