Swift Chartsまとめ

# iOS18でのアップデート
以下のスライドにまとめた
[![thumbnail](https://files.speakerdeck.com/presentations/05621d2a2bfd4cb7820a9876831b728f/slide_0.jpg)](https://speakerdeck.com/yamakentoc/swift-charts-vectorized-and-function-plots)

# これは何？

[Swift Chartsのドキュメント](https://developer.apple.com/documentation/charts "")を上から下まで読んである程度自分なりにまとめたものになります。なのでこの記事を読んでChartsを知ろうというよりは、実装するときの参考資料程度に見た方がいいかと思います。
もちろんドキュメントの内容全てを網羅してる訳でもないので、随時更新しようと思います。

## 注意事項
本記事に記載しているAPIなどについては、X軸用/Y軸用とそれぞれあります。
全て書くと冗長になるので、X軸のAPIについて記載がある場合は、Y軸のAPIもあるんだなと思いながら見てください。

# 基礎
日本語ではグラフのことを「グラフ」というが、Charts内ではグラフのことをチャートという。
棒グラフは”bar chart”、折れ線グラフは”line chart”といった感じ。
また、以下のような用語がある
<img width="400" alt="スクリーンショット 2024-05-20 22.38.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/556d3cfa-931b-1866-48d8-6f4b81b71a83.png">

- Mark
  - データポイントを示す1つ1つの視覚的な形状のこと。下の図の青い棒1つをMarkという
- Plot area
  - chartの描画領域
- Grid line
  - chartの背景に表示される線
- Axis
  - グラフの縦軸横軸を示す
- Axis value label (tick label)
  - 軸に沿って配置される数値やカテゴリーのLabel
- Tick
  - Grid lineの延長上に表示されるもので、Axis value labelの正確な位置を示すもの

## 基本的な定義方法
Chart内に描画したいMarkを定義するだけ。
```Swift
import SwiftUI
import Charts

struct ContentView: View {
    var body: some View {
        Chart {
            BarMark(
                x: .value("Category", "A"),
                y: .value("value", 100)
            )
            BarMark(
                x: .value("Category", "B"),
                y: .value("value", 200)
            )
        }
        .frame(height: 200)
        .padding()
    }
}
```

<img width="300" alt="スクリーンショット 2024-05-20 22.48.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/658674d5-0cd1-c9ab-355a-5ddb0fb4fde6.png">

BarMarkの引数x, yにそれぞれ表示するカテゴリとその値を指定する。
各軸の情報は指定しなくてもデータに基づいて最適な情報を描画してくれる。

## ForEachの使用
実際には1つ1つBarMark()を指定しないので、以下のようにまとめて定義する
```Swift
struct Data: Identifiable {
    var id: String { category }
    let category: String
    let value: Int
}

struct ContentView: View {
    
    let data: [Data] = [
        .init(category: "A", value: 100),
        .init(category: "B", value: 200)
    ]
    
    var body: some View {
        Chart {
            ForEach(data) {
                BarMark(
                    x: .value("Category", $0.category),
                    y: .value("value", $0.value)
                )
            }
        }
    }
}
```
Chart内で扱うのがForEachのデータのみの場合、以下のように指定も可能。

```Swift
Chart(data) {
    BarMark(
        x: .value("Category", $0.category),
        y: .value("value", $0.value)
    )
}
// dataがidentifiableに準拠してない場合Chart(data, id: \.category)のように指定も可能
```
## グラフの向きを変える
xとyの値を入れ替えると描画も横向きに変わる

```Swift
BarMark(
    x: .value("value", $0.value),
    y: .value("Category", $0.category)
)
```

![スクリーンショット 2024-05-20 22.53.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/60ae7510-6057-087b-748c-5c61c04bc682.png)

## Markの種類
以下のMarkを通して各chartを実装できる。これに加えてiOS17からはSectorMark（いわゆる円グラフ）が実装可能になった。
![スクリーンショット 2024-03-29 17.20.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/1cf30392-798d-72bc-2393-a65090de1479.png)
複雑なchartもこのMarkを組み合わせて実装する。

# BarMark
基本は前述した通りの[BarMark(x:y:)](https://developer.apple.com/documentation/charts/barmark/init(x:y:width:height:stacking:))。これ以外にもイニシャライザとその引数によって様々なBarMarkが表現できる
```Swift
BarMark(
    x: .value("Category", $0.category),
    y: .value("value", $0.value)
)
```

## BarMarkのイニシャライザ1
[BarMark(x:y:)](https://developer.apple.com/documentation/charts/barmark/init(x:y:width:height:stacking:))のイニシャライザをよく見ると、デフォルト引数として以下のような指定も可能となっている。
(MarkDimension、MarkStackingMethodについては後述)
```Swift
init<X, Y>(
    x: PlottableValue<X>, // x軸の値
    y: PlottableValue<Y>,　// ｙ軸の値
    width: MarkDimension = .automatic, // Markの横幅
    height: MarkDimension = .automatic, // Markの縦幅
    stacking: MarkStackingMethod = .standard // stackの方法
) where X : Plottable, Y : Plottable
```
例えば以下のような表現も可能となる

```Swift
BarMark(
    x: .value("Category", $0.category),
    y: .value("value", $0.value),
    width: .fixed(30), // 横幅を30に固定
    stacking: .center // 各Markの中心点が中央に来るように
)
```

![スクリーンショット 2024-05-20 23.05.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/fa2100e8-44b5-d542-9e32-0de86ecb729b.png)

## BarMarkのイニシャライザ2
range bar chart（範囲型棒グラフ）を生成するのに使用する

```Swift
init<X, Y>(
    x: PlottableValue<X>,// x軸の値
    yStart: PlottableValue<Y>, // そのMarkのminの値
    yEnd: PlottableValue<Y>, // そのMarkのmaxの値
    width: MarkDimension = .automatic // Markの横幅
) where X : Plottable, Y : Plottable
```

```Swift
Chart(data, id: \.date) {
    BarMark(
        x: .value("Month", $0.date, unit: .month),
        yStart: .value("Daily Min", $0.dailyMin),
        yEnd: .value("Daily Max", $0.dailyMax),
        width: .init(floatLiteral: 20)
    )
}
```

![スクリーンショット 2024-05-20 23.16.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/1a2936d5-3e1c-178a-3f36-5a7c83a46937.png)

## BarMarkのイニシャライザ3
x軸かy軸を固定したchartを生成する。
iPhoneの設定アプリで見れるstorageの表現などに使用。

```Swift
init<X>(
    x: PlottableValue<X>,
    yStart: CGFloat? = nil, // その値の開始位置。nilの場合は左に揃える
    yEnd: CGFloat? = nil, // その値の終了位置
    width: MarkDimension = .automatic,// Markの横幅
    stacking: MarkStackingMethod = .standard // stackの方法
) where X : Plottable
```

```Swift
let data: [FileCategory] = [
    .init(fileSizePercent: 50, fileCategory: "Applications"),
    .init(fileSizePercent: 10, fileCategory: "Photos"),
    .init(fileSizePercent: 5, fileCategory: "Music"),
    .init(fileSizePercent: 3, fileCategory: "Podcasts"),
    .init(fileSizePercent: 10, fileCategory: "iOS"),
    .init(fileSizePercent: 10, fileCategory: "System Data"),
]

public var body: some View {
    VStack {
        Chart(data, id: \.fileCategory) {
            BarMark(
                x: .value("File Size Percent", $0.fileSizePercent)
            )
            .foregroundStyle(by: .value("File Category",$0.fileCategory))
        }
        .chartXAxis(.hidden) // x軸を非表示
        .frame(height: 50)
    }
}
```
.foregroundStyle(by:)を用いることでグループごとに色を変えることができる。
自動的にlegend(凡例)も付与される。

<img width=“200” alt="simulator_screenshot_8F60CEEB-508D-4146-9ECE-42D9BCB6F459.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/961e5978-9a73-3222-1ef7-f1140697c28d.png">

## BarMarkのイニシャライザ4
 Y軸固定で、Xの指定した開始位置/終了位置で描画する（X軸固定のイニシャライザもあり）

```Swift
init<X>(
    xStart: PlottableValue<X>,
    xEnd: PlottableValue<X>,
    yStart: CGFloat? = nil,
    yEnd: CGFloat? = nil
) where X : Plottable
```

```Swift
let tasksData: [WorkHours] = [
    WorkHours(task: "Task A", startHour: 9, endHour: 13),
    WorkHours(task: "Task B", startHour: 14, endHour: 16),
    WorkHours(task: "Task C", startHour: 17, endHour: 19),
]

public var body: some View {
    Chart(tasksData, id: \.task) {
        BarMark(
            xStart: .value("Start Time", $0.startHour),
            xEnd: .value("End Time", $0.endHour)
        )
    }
    .frame(height: 50)
}
```

<img width=“300” alt="スクリーンショット 2024-05-20 23.25.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/954ca483-b695-15d2-18b5-daf6ef5f64cf.png">

## 積み上げ棒グラフ
.foregroundStyle(by:)を使うことでシンプルなStacked Bar Chartを作れる。

```Swift
struct DataSummary: Identifiable {
    var id: String { name }
    var name: String
    var dataList: [Data]
}

struct Data: Identifiable {
    var id: String { category }
    let category: String
    let value: Int
}

struct ContentView: View {
    let hogeDataList: [Data] = [
        .init(category: "A", value: 100),
        .init(category: "B", value: 200)
    ]
    let piyoDataList: [Data] = [
        .init(category: "A", value: 50),
        .init(category: "B", value: 80)
    ]
    
    var dataSummaryList: [DataSummary] {[
        .init(name: "hoge", dataList: hogeDataList),
        .init(name: "piyo", dataList: piyoDataList)
    ]}
    
    var body: some View {
        Chart(dataSummaryList) { dataSummary in
            ForEach(dataSummary.dataList) { data in
                BarMark(
                    x: .value("Category", data.category),
                    y: .value("value", data.value)
                )
                // nameごとに色を変える(ここでは色の指定ができない)
                .foregroundStyle(by: .value("name", dataSummary.name))
            }
        }
        // 色の指定をする
        .chartForegroundStyleScale([
             "hoge": .green, "piyo": .red
         ])
    }
}
```

![スクリーンショット 2024-05-20 23.39.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/f8a370cc-b93b-658d-9a55-8089b7d582c5.png)

## グループ化された棒グラフ
.positionを用いることでグループ化したchartを作れる

```Swift
BarMark(
    x: .value("Category", data.category),
    y: .value("value", data.value)
)
.foregroundStyle(by: .value("name", dataSummary.name))
// nameごとにグループ化したchartを作る
.position(by: .value("name", dataSummary.name))
```

![スクリーンショット 2024-05-20 23.39.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/003062b9-e8c7-7331-3043-3f843b4a79f2.png)

# LineMark
折れ線グラフを描画するのに使用

```Swift
Chart(hogeDataList) {
	LineMark(
        x: .value("Category", $0.category),
        y: .value("value", $0.value)
	)
}
```

![スクリーンショット 2024-05-20 23.46.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/7ca95137-f9cb-a1f8-134c-2df62381fa9f.png)

### LineMarkのイニシャライザ
複数のデータを扱う場合、以下のようにseries引数を用いることで区別できる。これを指定しない場合、線がつながってしまうので注意。

```Swift
Chart(dataSummaryList) { dataSummary in
    ForEach(dataSummary.dataList) { data in
        LineMark(
            x: .value("Category", data.category),
            y: .value("value", data.value),
            series: .value("name", dataSummary.name) // データの区別
        )
    }
}
```

![スクリーンショット 2024-05-20 23.49.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/8ed0f88c-c0f0-1ed7-30c1-f36f9ca76ca7.png)

series引数を使わなくても.foregroundStyleを使うことで分けることは可能。

```Swift
LineMark(
    x: .value("Category", data.category),
    y: .value("value", data.value)
)
.foregroundStyle(by: .value("name", dataSummary.name))
```

![スクリーンショット 2024-05-20 23.52.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/47ce544b-26a3-2f73-058d-30f960032426.png)

# PointMark
散布図に使ったり、他のMarkと組み合わせて使うことが多そう

```Swift
Chart(hogeDataList) {
    PointMark(
        x: .value("Category", $0.category),
        y: .value("value", $0.value)
    )
}
```

![スクリーンショット 2024-05-20 23.53.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/daeed136-017e-54fc-47cb-9630ca583507.png)

## LineMarkとPointMarkの組み合わせ
LineMarkと組み合わせる場合、以下のように書くことも可能だが、

```Swift
Chart(hogeDataList) {
    LineMark(
        x: .value("Category", $0.category),
        y: .value("value", $0.value)
    )
    PointMark(
        x: .value("Category", $0.category),
        y: .value("value", $0.value)
    )
}
```

![スクリーンショット 2024-05-20 23.55.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/9057991c-4788-8f63-2439-b294b3f2e67d.png)


折れ線でPointを表示したい場合は.symbolのみで表示することも可能
```Swift
LineMark(
    x: .value("Category", $0.category),
    y: .value("value", $0.value)
)
.symbol(.circle)
```
## PointMarkの別のイニシャライザ

```Swift
init<X>(
    x: PlottableValue<X>, // x軸の値
    y: CGFloat? = nil  // yの位置。
) where X : Plottable
```

```Swift
Chart(hogeDataList) {
    PointMark(
        x: .value("value", $0.value)
    )
}
.frame(height: 100)
```

![スクリーンショット 2024-05-20 23.57.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/518fbb0f-33cd-bc27-b185-d1e6d1fadf0c.png)

# AreaMark
面グラフを作れる

```Swift
let cheeseburgerCostByItem: [Food] = [
    .init(name: "Burger", price: 0.07, year: 1960),
    .init(name: "Cheese", price: 0.03, year: 1960),
    .init(name: "Bun", price: 0.05, year: 1960),
    .init(name: "Burger", price: 0.10, year: 1970),
    ...
]
...

Chart(cheeseburgerCost) {
    AreaMark(
        x: .value("Date", $0.date),
        y: .value("Price", $0.price)
    )
}
```

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/093f8ae1-860d-cd36-0649-0b5f1aa8237e.png" alt="Simulator Screenshot - iPhone 15 Pro - 2024-05-11 at 10.53.26">

foregroundStyleを使って積み上げ面グラフを作ったりも可能

```Swift
Chart(cheeseburgerCostByItem) {
    AreaMark(
        x: .value("Date", $0.date),
        y: .value("Price", $0.price)
    )
    .foregroundStyle(by: .value("Food Item", $0.name))
}
```

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/830aea84-ba36-cefe-8895-e278f3229e93.png" alt="Simulator Screenshot - iPhone 15 Pro - 2024-05-11 at 10.55.06">

## AreaMarkのイニシャライザ1
イニシャライザを見ると他にも引数がある

```Swift
init<X, Y, S>(
    x: PlottableValue<X>,
    y: PlottableValue<Y>,
    series: PlottableValue<S>, // データの区別
    stacking: MarkStackingMethod = .standard // stackの方法
) where X : Plottable, Y : Plottable, S : Plottable
```

正規化した面グラフも作れる

```Swift
Chart(cheeseburgerCostByItem) {
    AreaMark(
        x: .value("Date", $0.date),
        y: .value("Price", $0.price),
        stacking: .normalized // 正規化
    )
    .foregroundStyle(by: .value("Food Item", $0.name))
}
```

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/77df1f79-a5b2-fd6d-7929-4882416a9cf9.png" alt="Simulator Screenshot - iPhone 15 Pro - 2024-05-11 at 10.57.13">

## AreaMarkのイニシャライザ2
Y軸の開始位置と終了位置を指定したグラフを作れる

```Swift
init<X, Y>(
    x: PlottableValue<X>,
    yStart: PlottableValue<Y>, // y軸での面の開始位置
    yEnd: PlottableValue<Y> // y軸での面の終了位置
) where X : Plottable, Y : Plottable
```

```Swift
let data: [Weather] = [
    .init(month: 1, maximumTemperature: 10, minimumTemperature: 0),
    .init(month: 2, maximumTemperature: 12, minimumTemperature: 1),
    .init(month: 3, maximumTemperature: 15, minimumTemperature: 6),
    ...
]
...
Chart(data) {
    AreaMark(
        x: .value("Date", $0.date),
        yStart: .value("Minimum Temperature", $0.minimumTemperature),
        yEnd: .value("Maximum", $0.maximumTemperature)
    )
}
```

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/f0cdd8e1-7bc3-52e8-bc47-16504fba2606.png" alt="スクリーンショット 2024-05-21 0.09.19">

# RuleMark
水平、垂直の罫線を使用してデータを表す

## RuleMarkのイニシャライザ1

```Swift
init<X>(
    x: PlottableValue<X>, // X軸上の罫線の表示位置
    yStart: CGFloat? = nil, // 罫線の開始位置
    yEnd: CGFloat? = nil // 罫線の終了位置
) where X : Plottable
```

```Swift
Chart(sales) {
    BarMark(
        x: .value("Profit", $0.profit),
        y: .value("Department", $0.department)
    )
    RuleMark(x: .value("average", 100000))
        .foregroundStyle(.red)
}
```

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/e91397cb-00f4-6929-b0e2-ed5316a432df.png" alt="Simulator Screenshot - iPhone 15 Pro - 2024-05-11 at 11.58.25">

yStart, yEndも入れると長さを調整できる↓

```Swift
RuleMark(x: .value("average", 100000), yStart: 100, yEnd: 300)
```

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/2835060c-0c31-7bc3-94cf-0bc85b6a015c.png" alt="Simulator Screenshot - iPhone 15 Pro - 2024-05-11 at 12.03.04">

## RuleMarkのイニシャライザ2
```Swift
init<X, Y>(
    x: PlottableValue<X>, // X軸上の罫線の表示位置
    yStart: PlottableValue<Y>, // 罫線の開始位置
    yEnd: PlottableValue<Y> // 罫線の終了位置
) where X : Plottable, Y : Plottable
```

```Swift
RuleMark(
    x: .value("Pollen Source", $0.source),
    yStart: .value("Start Date", $0.startDate),
    yEnd: .value("End Date", $0.endDate)
)
RuleMark(
    y: .value("average", averageDate)
)
.foregroundStyle(.red)
```

<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/bf7166a2-053f-bfb1-8c5a-6a362c308f89.png" alt="Simulator Screenshot - iPhone 15 Pro - 2024-05-11 at 11.48.42">

## RuleMarkのイニシャライザ3

```Swift
init<Y>(
    x: CGFloat? = nil,
    yStart: PlottableValue<Y>,
    yEnd: PlottableValue<Y>
) where Y : Plottable
```

```Swift
RuleMark(
    yStart: .value("Start Date", data[0].startDate),
    yEnd: .value("End Date", data[0].endDate)
)
RuleMark(
    x: 60, // 指定した位置に表示する
    yStart: .value("Start Date", data[1].startDate),
    yEnd: .value("End Date", data[1].endDate)
)
```

<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/e540c5f4-08cc-1d43-2d23-d3dc7219a82c.png" alt="Simulator Screenshot - iPhone 15 Pro - 2024-05-11 at 12.18.15">

# RectangleMark
ヒートマップなどに使う

## RectabgleMarkのイニシャライザ1
```Swift
init<X, Y>(
    x: PlottableValue<X>,
    y: PlottableValue<Y>,
    width: MarkDimension = .automatic, // Markのwidth
    height: MarkDimension = .automatic // Markのheight
) where X : Plottable, Y : Plottable
```

```Swift
var data: [MatrixEntry] = [
    .init(positive: "+", negative: "+", num: 125),
    .init(positive: "+", negative: "-", num: 10),
    .init(positive: "-", negative: "-", num: 80),
    .init(positive: "-", negative: "+", num: 1)
]

public var body: some View {
    Chart(data) {
        RectangleMark(
            x: .value("Positive", $0.positive),
            y: .value("Negative", $0.negative)
        )
        .foregroundStyle(by: .value("Number", $0.num))
    }
    .aspectRatio(contentMode: .fit)
    .padding()
}
```

<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/99e62e79-7cfe-e329-e824-570cf47c211d.png" alt="Simulator Screenshot - iPhone 15 Pro - 2024-05-11 at 12.47.58">


```Swift
Chart(data) {
    RectangleMark(
        x: .value("Rect X", 3),
        y: .value("Rect Y", 3),
        width: .fixed(25),
        height: .fixed(25)
    )
    .opacity(0.2)
    PointMark(
        x: .value("X", $0.x),
        y: .value("Y", $0.y)
    )
}
.aspectRatio(contentMode: .fit)
```

<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/24400cfd-e8a1-fde5-48a1-1e3931fb2831.png" alt="Simulator Screenshot - iPhone 15 Pro - 2024-05-11 at 12.56.24">


## RectabgleMarkのイニシャライザ2

```Swift
init<X, Y>(
    x: PlottableValue<X>, // X軸上の位置
    yStart: PlottableValue<Y>, // Y軸上の開始位置
    yEnd: PlottableValue<Y>, // // Y軸上の終了位置
    width: MarkDimension = .automatic
) where X : Plottable, Y : Plottable
```

```Swift
RectangleMark(
    x: .value("Rect X", 4),
    yStart: .value("Rect yStart", 2),
    yEnd: .value("Rect yEnd", 4),
    width: .fixed(30)
)
```

<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/9f477cd5-e41e-7690-3395-6f83429d69c4.png" alt="Simulator Screenshot - iPhone 15 Pro - 2024-05-11 at 12.50.31">


## RectabgleMarkのイニシャライザ3

```Swift
init<X, Y>(
    xStart: PlottableValue<X>,
    xEnd: PlottableValue<X>,
    yStart: PlottableValue<Y>,
    yEnd: PlottableValue<Y>
) where X : Plottable, Y : Plottable
```

```Swift
RectangleMark(
    xStart: .value("Rect xStart", 2),
    xEnd: .value("Rect xEnd", 4),
    yStart: .value("Rect yStart", 2),
    yEnd: .value("Rect yEnd", 4)
)
```

<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/810625f9-caae-bead-d94c-ff4edb312a3e.png" alt="Simulator Screenshot - iPhone 15 Pro - 2024-05-11 at 12.53.14">


## RectabgleMarkのイニシャライザ4

```Swift
init<X>(
    x: PlottableValue<X>,
    yStart: CGFloat? = nil,
    yEnd: CGFloat? = nil,
    width: MarkDimension = .automatic
) where X : Plottable
```

```Swift
RectangleMark(
    x: .value("Rect X", 3)
)
RectangleMark(
    y: .value("Rect Y", 1)
)
.foregroundStyle(.red)

PointMark(
    x: .value("X", $0.x),
    y: .value("Y", $0.y)
)
```

<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/50230d26-65db-9415-2526-09c9e712b47a.png" alt="Simulator Screenshot - iPhone 15 Pro - 2024-05-11 at 13.03.09">


## RectabgleMarkのイニシャライザ5

```Swift
init(
    xStart: CGFloat? = nil,
    xEnd: CGFloat? = nil,
    yStart: CGFloat? = nil,
    yEnd: CGFloat? = nil
)
```

```Swift
RectangleMark()
    .opacity(0.2)
```

<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/2b59721d-b949-3bb1-bd45-bd82fd63e0e3.png" alt="Simulator Screenshot - iPhone 15 Pro - 2024-05-11 at 13.04.28">


## RectabgleMarkのイニシャライザ6

```Swift
init<Y>(
    xStart: CGFloat? = nil,
    xEnd: CGFloat? = nil,
    yStart: PlottableValue<Y>,
    yEnd: PlottableValue<Y>
) where Y : Plottable
```

```Swift
RectangleMark(
    yStart: .value("Rect yStart", 2),
    yEnd: .value("Rect yEnd", 4)
)
```

<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/210d19aa-5030-3d5c-7197-daa496a88a59.png" alt="Simulator Screenshot - iPhone 15 Pro - 2024-05-11 at 13.05.55">


# SectorMark
扇形グラフ（円グラフ・ドーナツグラフ）を描画する

```Swift
init(
    angle: PlottableValue<some Plottable>, // データの値
    innerRadius: MarkDimension = .automatic, // 内円の半径の大きさ
    outerRadius: MarkDimension = .automatic, // 外円の半径の大きさ
    angularInset: CGFloat? = nil // 項目ごとの区切りの大きさ
)
```

引数angleに値を入れることで自動的に正規化された状態で表示される
`.foregroundStyle`などでカテゴリーごとに色を変えないと全て青になるので注意

```Swift
Chart(data) {
    SectorMark(angle: .value("Value", $0.sales))
        .foregroundStyle(by: .value("Food Category", $0.name))
}
```

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/273c6db5-efa4-342a-1ce8-7f8eca4ec2cf.png" alt="スクリーンショット 2024-05-25 12.19.29">

innerRadiusを指定すると内円ができ、outerRadiusを指定すると外円ができる

```Swift
Chart(data) {
    SectorMark(
        angle: .value("Value", $0.sales),
        innerRadius: .ratio(0.6),
        outerRadius: .inset(60), 
        angularInset: 1
    )
    .foregroundStyle(by: .value("Food Category", $0.name))
}
```

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/9967abf4-0b08-f6cd-3daa-3b49343487ca.png" alt="スクリーンショット 2024-05-25 12.27.45">

chartBackgroundを使って内円の中心にTextを表示することもできる

```Swift
Chart {
	SectorMark(...)
}
.chartBackground { chartProxy in
    GeometryReader { geometry in
        let frame = geometry[chartProxy.plotAreaFrame]
        VStack {
            Text("Most Sold Style")
                .font(.callout)
                .foregroundStyle(.secondary)
            Text("Cachapa")
                .font(.title2.bold())
                .foregroundStyle(.primary)
        }
        .position(x: frame.midX, y: frame.midY)
    }
}
```

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/f24b965b-3800-f210-1e41-1a0204cf4b9a.png" alt="スクリーンショット 2024-05-25 12.45.35">

# ChartContentで使えるメソッド
BarMarkやLineMarkなどのMarkはChartContentプロトコルに準拠している。
このChartContentに定義されているメソッドを通じてMarkのカスタマイズができる。

## スタイリング系

### グラフの色を変える

```Swift
BarMark(
...
)
.foregroundStyle(.red)
```

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/23723be0-c42b-7441-884d-347edec49408.png" alt="Simulator Screenshot - iPhone 15 - 2024-05-04 at 12.48.28">

ShapeStyleを指定するのでグラデーションの指定も可能

```Swift
BarMark(
...
)
.foregroundStyle(
    LinearGradient(gradient: Gradient(colors: [.blue, .purple]), startPoint: .top, endPoint: .bottom)
)
```

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/924e9bec-b628-1a42-6c23-8bed4e64119b.png" alt="Simulator Screenshot - iPhone 15 - 2024-05-04 at 12.59.11">

### styleをplotAreaに適用する
MarkのstyleをplotArea（表示領域）全体に合わせる
Markにグラデーションを適用した場合、plotArea全体をグラデーションの開始点/終了点に利用することになる

```Swift
BarMark(
...
)
.foregroundStyle(
    LinearGradient(gradient: Gradient(colors: [.blue, .purple]), startPoint: .top, endPoint: .bottom)
)
.alignsMarkStylesWithPlotArea()
```
一番左のMarkを見ると先ほどのMarkよりグラデーションのかかり方が異なるのがわかる
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/021b8c3d-700a-b0b6-10ca-ec51b350ff79.png" alt="Simulator Screenshot - iPhone 15 - 2024-05-04 at 13.02.35">

### 不透明度の設定

```Swift
BarMark(
...
)
.opacity(0.3)
```

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/1fd9559b-3593-bbff-9bd3-41b896423430.png" alt="Simulator Screenshot - iPhone 15 - 2024-05-04 at 12.56.20">

### 角丸にする

```Swift
BarMark(
...
)
.cornerRadius(15)
```

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/0fd8ca57-4592-6cbd-3db0-6125528a64d0.png" alt="Simulator Screenshot - iPhone 15 - 2024-05-04 at 13.17.22">

### LineMarkのスタイルの設定
StrokeStyleに準拠していればいいので色々設定できそう。

```Swift
LineMark(
...
)
// 線の太さを変更
.lineStyle(.init(lineWidth: 10))
```

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/20682f28-d357-fc80-3569-128260529b57.png" alt="Simulator Screenshot - iPhone 15 - 2024-05-04 at 15.31.34">

### 曲線などにする
指定した補間(データポイント間の線や曲線を滑らかにする処理)方法でLineMarkとAreaMarkを描画する
```Swift
LineMark(
...
)
// 滑らかな曲線
.interpolationMethod(.catmullRom)
```

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/6644efe2-a964-3c13-b812-de911ca07bb3.png" alt="Simulator Screenshot - iPhone 15 - 2024-05-04 at 15.42.15">

- .linear
    - 直線
- .catmullRom
    - 滑らかな曲線
- .catmullRom(alpha:)
    - 曲線の形状を制御する。0~1の間で値が高いほど曲線がデータポイントを厳密に通過しようとし、低いほどより滑らかな曲線になる（データポイントから外れる？）
- .cardinal
    - .catmullRomより緩やかな曲線
- .cardinal(tension:)
    - tension(張力)を指定できる。0~1の間で値が低いほど滑らかになり、高いほど直線になる
- .monotone
    - 例外な値があっても不自然なひねりを避ける滑らかな曲線
- .stepCenter
    - 階段状に表示する。データポイントはステップの中心になる

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/efe43f58-e7fe-2200-3526-2091d3398a94.png" alt="Simulator Screenshot - iPhone 15 - 2024-05-04 at 15.49.17">

- .stepStart
    - 階段状に表示する。データポイントはステップの開始位置になる
- .stepEnd
    - 階段状に表示する。データポイントはステップの終了位置になる

### グラフの後ろにViewを定義する

```Swift
Chart {
...
}
.chartBackground { _ in
    Rectangle()
        .frame(width: 200, height: 100)
        .foregroundColor(.red)
}
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/a2883da0-12d4-fd04-3d4b-b89ba5753cbc.png" alt="スクリーンショット 2024-05-12 20.18.53">

### グラフの前面にViewを定義する

```Swift
Chart {
...
}
.chartOverlay { _ in
    Rectangle()
        .frame(width: 200, height: 100)
        .foregroundColor(.red)
}
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/7790dccb-d135-36aa-5e62-cd5bdafdcfe2.png" alt="スクリーンショット 2024-05-26 11.06.03">


### カテゴリに基づいてグラフを指定した色にする

```Swift
Chart(salesByCityData) { citySale in
    ForEach(citySale.sales) {
        BarMark(
            x: .value("Day", $0.weekday, unit: .day),
            y: .value("Sales", $0.sales)
        )
        .foregroundStyle(by: .value("City", citySale.city))
    }
}
// カテゴリーに対して異なる色を指定する
.chartForegroundStyleScale([
    "Cupertino": .green, "San Francisco": .red
])
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/b8a0a98a-b594-a28c-e1c0-b2f32fb0a3e7.png" alt="スクリーンショット 2024-05-12 20.23.28">

複数の指定方法がある（詳細は[ドキュメント](https://developer.apple.com/documentation/swiftui/view/chartforegroundstylescale(_:) "")）

```Swift
struct StorageChartView: View {
    struct FileCategory {
        let fileSizePercent: Double
        let fileCategory: String
        let color: Color
    }

    let data: [FileCategory] = [
        .init(fileSizePercent: 50, fileCategory: "Applications", color: .red),
        .init(fileSizePercent: 10, fileCategory: "Photos", color: .orange),
        .init(fileSizePercent: 5, fileCategory: "Music", color: .yellow),
        .init(fileSizePercent: 3, fileCategory: "Podcasts", color: .green),
        .init(fileSizePercent: 10, fileCategory: "iOS", color: .gray),
        .init(fileSizePercent: 10, fileCategory: "System Data", color: .black),
    ]
 
    var body: some View {
        Chart(data, id: \.fileCategory) {
            BarMark(
                x: .value("File Size Percent", $0.fileSizePercent)
            )
            .foregroundStyle(by: .value("File Category", $0.fileCategory))
        }
        .chartForegroundStyleScale(domain: data.map { $0.fileCategory }, range: data.map { $0.color })
        .chartXAxis(.hidden)
        .frame(height: 50)
        .padding()
    }
}
```

<img width="450" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/341ac572-4fac-f4b8-9be3-aa9801d65707.png" alt="スクリーンショット 2024-05-12 20.40.26">

### plotAreaのスタイリングをする

```Swift
Chart {
...
}
.chartPlotStyle { content in
    content
        .background(.gray.opacity(0.2)) // 背景色を追加
        .border(.black) // 境界線を追加
}
```

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/4290ced0-a6f9-9fb7-60a4-a12bd12ce106.png" alt="スクリーンショット 2024-05-12 20.53.34">

## 位置系

### offset
Markの位置をずらす
```Swift
BarMark(
    x: .value("Category", $0.category),
    y: .value("value", $0.value)
)
// 右に50、下に50ずらす
.offset(CGSize(width: 50, height: 50))
// .offset(x: 50, y: 50)
```

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/b2cb1857-3290-b6ca-805b-5334b69de0fa.png" alt="Simulator Screenshot - iPhone 15 - 2024-05-04 at 16.04.57">

### offset(x:yStart:yEnd:)
MarkのY軸の開始位置と終了位置のそれぞれに異なるoffsetを指定し、さらにX軸にoffsetを指定できる。

```Swift
BarMark(
...
)
.offset(x: 0, yStart: 0, yEnd: 10)
```

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/c8277230-d8f2-e1c3-9b9b-5b32fee4447a.png" alt="Simulator Screenshot - iPhone 15 - 2024-05-04 at 16.18.50">

### offset(xStart:xEnd:y:)
MarkのX軸の開始位置と終了位置のそれぞれに異なるoffsetを指定し、さらにY軸にoffsetを指定できる

```Swift
BarMark(
...
)
// 各MarkのX軸の終了位置を20右にずらす
.offset(xStart: 0, xEnd: 20, y: 0)
```

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/dc579b90-cd6d-0843-3fbc-7d202f6ecc9b.png" alt="Simulator Screenshot - iPhone 15 - 2024-05-04 at 16.15.20">

### offset(xStart:xEnd:yStart:yEnd:)
Markのx軸とy軸それぞれの開始位置と終了位置を指定できる

## シンボルの外観の設定

### シンボルの外観を変える
指定できるのはChartSymbolShapeに準拠したもの

```Swift
PointMark(
...
)
.symbol(.asterisk)
```

- .symbol(.asterisk) アスタリスク
<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/76854a10-64fe-2711-ba61-7ff16e147a40.png" alt="スクリーンショット 2024-05-11 15.41.21">

- .symbol(.circle) 円
<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/9b2531a3-a7b8-f39e-a675-e4df2e9dd7b4.png" alt="スクリーンショット 2024-05-11 15.43.10">

- .symbol(.cross) クロス(×)
<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/33380ff5-a92c-2659-9ae1-3cab27f1e076.png" alt="スクリーンショット 2024-05-11 15.43.56">

- .symbol(.diamond) ひし形
<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/df8808e4-6ba1-b303-de44-c7f20f183e5b.png" alt="スクリーンショット 2024-05-11 15.47.40">

- .symbol(.pentagon) 五角形
<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/4fc06911-3305-8ecf-eeb8-dc2aa130d84e.png" alt="スクリーンショット 2024-05-11 15.47.58">

- .symbol(.plus) プラス(+)
<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/cd2f552e-9e8f-becb-83a1-146dcf3b19f9.png" alt="スクリーンショット 2024-05-11 15.48.23">

- .symbol(.square) 四角
<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/7b8758f1-f7a4-c78c-9cbd-7a869bd33cbd.png" alt="スクリーンショット 2024-05-11 15.48.46">

- .symbol(.triangle) 三角
<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/f40844c2-008d-a386-5324-818e48788cc2.png" alt="スクリーンショット 2024-05-11 15.49.00">

シンボルの線の太さを変えることや、破線にすることなども可能
```Swift
.symbol(.circle.strokeBorder(lineWidth: 1))
```

<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/60457ea1-a67e-5ad2-48d5-eb223206a463.png" alt="スクリーンショット 2024-05-11 15.58.22">

```Swift
.symbol(.circle.strokeBorder(style: StrokeStyle(lineWidth: 1, dash: [2, 2])))
```

<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/75c9d630-9807-5cff-3c02-edb3be1e4835.png" alt="スクリーンショット 2024-05-11 16.00.50">

### 任意のViewをシンボルにする

```Swift
PointMark(
...
)
.symbol {
    Image(systemName: "heart.fill")
        .foregroundStyle(.red)
}
```
<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/6fb452cb-025c-58eb-82bf-ab5b04cffa59.png" alt="スクリーンショット 2024-05-11 16.06.21">

### シンボルサイズの変更

```Swift
PointMark(
...
)
.symbol(.circle)
.symbolSize(10)
```

<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/57ae23de-0dc8-2ea2-e5db-b024ee5e3183.png" alt="スクリーンショット 2024-05-11 16.09.13">

小さくすることはいくらでも可能っぽいが、確認した限り一定値以上は大きくできない。
また、独自のシンボルを設定した場合はサイズが適用されない（独自シンボルのView側で指定すればよさそう）

### カテゴリーごとに異なるシンボルを設定する

```Swift
var salesByCityData: [SalesByCity] {[
    .init(city: "Cupertino", sales: cupertinoData),
    .init(city: "San Francisco", sales: sfData)
]}
...
Chart(salesByCityData) { citySale in
    ForEach(citySale.sales) {
        PointMark(
            x: .value("Day", $0.weekday, unit: .day),
            y: .value("Sales", $0.sales)
        )
        .foregroundStyle(by: .value("City", citySale.city))
        .symbol(by: .value("City", citySale.city))
    }
}

// カテゴリーに対して異なるシンボルを適用する
.chartSymbolScale([
    "Cupertino": Circle(), "San Francisco": Circle().strokeBorder(lineWidth: 1)
])
```

<img width="350" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/377338bf-48fa-eac7-ce34-b7fb90403357.png" alt="スクリーンショット 2024-05-26 12.28.22">

### カテゴリーごとに異なるシンボルサイズを適用する?
[chartSymbolSizeScale(_:)](https://developer.apple.com/documentation/swiftui/view/chartsymbolsizescale(_:) "")というのがあるが、使い方がよくわからない

## データごとに外観を変える

### foregroundStyle(by:)
指定したデータの値ごとに異なる色やスタイルを適用する
```Swift
BarMark(
    x: .value("Department", $0.department),
    y: .value("Profit", $0.profit)
  )
  .foregroundStyle(by: .value("Department", $0.department))
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/ba0aafa6-3297-e913-fef1-64d26f8c47e2.png" alt="スクリーンショット 2024-04-09 0.49.42">

### lineStyle(by:)
指定したデータの値ごとに異なる線のスタイル(実線や破線)を適用する

```Swift
Chart(departmentProfits) { departmentProfit in
    ForEach(departmentProfit.profitOverTIme, id: \.date) {
       LineMark(
          x: .value("Date", $0.date, unit: .month),
          y: .value("Profit", $0.profit)
       )
       .lineStyle(by: .value("departmentName", departmentProfit.name))
    }
}
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/aa6118c3-cb68-bda0-3b40-f941fcad15f8.png" alt="スクリーンショット 2024-04-09 1.02.42">

### chartLineStyleScale
`lineStyle(by:)`に加え、具体的にどのスタイルを適用するかを指定する
```Swift
Chart(departmentProfits) { departmentProfit in
    ForEach(departmentProfit.profitOverTIme, id: \.date) {
       LineMark(
          x: .value("Date", $0.date, unit: .month),
          y: .value("Profit", $0.profit)
       )
       .lineStyle(by: .value("departmentName", departmentProfit.name))
    }
}
.chartLineStyleScale([
    "A": StrokeStyle(lineWidth: 2),
    "B": StrokeStyle(lineWidth: 5, dash: [5, 5])
])
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/bd5d5529-da63-a13a-9da2-21366d6238db.png" alt="スクリーンショット 2024-05-26 13.18.09">


### position(by:axis:span:)
指定したデータの値ごとにMarkの位置を決定する
axisで.horizontalと.verticalのどちらに沿ってMarkを配置するかを決める
spanでMarkが占める範囲を指定する(MarkDimension)

```Swift
ForEach(stackedBarData) { shape in
   BarMark(
      x: .value("Shape Type", shape.type),
      y: .value("Total Count", shape.count)
   )
   // 各BarMark内の要素に応じて色を変える
   .foregroundStyle(by: .value("Shape Color", shape.color))
   // 指定したデータごとにMarkの位置をずらす(.horizontal)
   .position(by: .value("Shape Color", shape.color), span: .fixed(100))
}
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/1066ae4d-7afd-23c2-9964-a6f417d9a78f.png" alt="スクリーンショット 2024-04-09 1.20.22">

axisで.verticalを指定すると通常の縦型になる
```Swift
.position(by: .value("Shape Color", shape.color), axis: .vertical ,span: .fixed(100))
```

### symbol(by:)
指定したデータの値ごとに異なるシンボルを設定する

```Swift
ForEach(departmentProfit.profitOverTIme, id: \.date) {
   LineMark(
      x: .value("Date", $0.date, unit: .month),
      y: .value("Profit", $0.profit)
   )
   .symbol(by: .value("departmentName", departmentProfit.name))
}
```

わかりにくいが円と四角のシンボルで分かれている
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/c6e67db6-5dcf-38f7-c6e9-f22c8c4dfa7c.png" alt="スクリーンショット 2024-04-09 1.28.38">

### symbolSize(by:)
指定したデータの値ごとに異なるサイズのシンボルを設定する

```Swift
Chart(participants, id: \.age) { participant in
   PointMark(
      x: .value("Age", participant.age),
      y: .value("Weight", participant.weight)
   )
   .symbolSize(by: .value("Weight", participant.weight))
}
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/9668df99-3fd7-9377-81e2-662f9496883f.png" alt="スクリーンショット 2024-05-11 16.39.10">


## 注釈をつける

### annotation(position:alignment:spacing:content:)
Markに対してViewで注釈をつけれる
- position: Markに対する相対的な位置
- alignment: 位置
- spacing: Markからどのくらい離すか

```Swift
Chart(sales) { sale in
    BarMark(
        x: .value("Department", sale.department),
        y: .value("Profit", sale.profit)
    )
    .annotation(position: .top, alignment: .center, spacing: 20) {
        Text("利益: \(sale.profit)")
            .font(.caption)
            .frame(width: 100, height: 40)
            .background(.gray)
    }
}
```

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/1fcc7e2d-94bb-d8d6-e1f2-19bda59fa062.png" alt="スクリーンショット 2024-04-09 2.12.59">

## Maskingとclipping

### Maskする
チャートのコンテンツの一部を強調したり、特定の範囲のみ表示するために使用

```Swift
BarMark(
    x: .value("Department", sale.department),
    y: .value("Profit", sale.profit)
)
.mask {
    if sale.department == "Marketing" {
        RectangleMark()
    } else {
        RectangleMark()
            .foregroundStyle(Color.black.opacity(0.5))
    }
}
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/f9b26e08-e735-23f4-e9a0-baa25d36f3c4.png" alt="スクリーンショット 2024-05-11 17.03.22">

### 角丸などにclippingする
Shapeに準拠していればいいので他にもいろんな形状にクリッピングできそう

```Swift
BarMark(
...
)
.clipShape(.rect(
    topLeadingRadius: 20,
    bottomLeadingRadius: 0,
    bottomTrailingRadius: 0,
    topTrailingRadius: 20
))
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/a72ded94-c68d-4b85-ed03-d766ed447606.png" alt="スクリーンショット 2024-05-11 17.05.09">

## Accessibilityの設定
https://developer.apple.com/documentation/charts/chartcontent/accessibilityhidden(_:)

## Instance Methods

### annotation(position:alignment:spacing:overflowResolution:content:)
注釈をつけるannotation(position:alignment:spacing:content:)と同じだが、overflowResolution引数が追加された。
通常だとPlotArea内に注釈を表示するが、グラフのPlotArea外に表示することが可能になる
iOS17+

```Swift
LineMark(...)
...
.annotation(
    position: .top,
    spacing: 0,
    overflowResolution: .init(
        x: .fit(to: .chart), // X軸では注釈がグラフの端の境界を超えないようにfitさせる
        y: .disabled // Y軸では注釈がグラフのすぐ上に来るようにするようにオーバーフロー解決を無効にする
    )
) {
    Text(...)
}
```

### Markをぼかす
特定のデータを強調表示して他の部分を目立たなくするために使用できる
iOS 16.4+

```Swift
BarMark(
...
)
.blur(radius: 4)
```
<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/f900f7d0-9ef2-6deb-693f-7bce24c0b77c.png" alt="スクリーンショット 2024-04-10 2.42.40">

### 各Markに対して処理を行う
iOS17+

```Swift
BarMark(
...
)
.compositingLayer {
     $0.opacity(0.5)
}
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/35ac4cc9-6795-e2da-462a-740ed7a98305.png" alt="スクリーンショット 2024-05-12 12.44.48">

### Markに影をつける
iOS16.4+

```Swift
BarMark(
...
)
.shadow(color: .red, radius: 2, x: 3, y: 5)
```
radius: 影のぼかしの強さ
<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/37454fe3-635d-cd7e-f6c2-31f44ebcd5c2.png" alt="スクリーンショット 2024-04-10 2.56.22">

### 重なっているMarkの表示順を指定する
指定する値が大きいほど手前に来る
```Swift
BarMark(
...
)
.zIndex(2) // BarMarkが手前
AreaMark(
...
)
.foregroundStyle(.red)
.zIndex(1)
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/a368c5e9-dbbb-dbc5-f7f6-8847dbf172f2.png" alt="スクリーンショット 2024-04-10 3.01.49">

# MarkStacking Method
MarkをStackする方法を指定する。BarMarkなどのMarkの一部のイニシャライザなどで指定することが可能。ドキュメント見ると良さそう

```Swift
BarMark(
    x: .value("Day", $0.weekday, unit: .day),
    y: .value("Sales", $0.sales),
    stacking: .standard // BarMarkでいうこの部分
)
.foregroundStyle(by: .value("City", citySale.city))
```

- .standard
    - 通常のStack方法

<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/f3ee6a50-0626-2b7b-b05c-96d41a328971.png" alt="スクリーンショット 2024-04-10 3.14.47">

- .center
    - 各Markの中心点が中央に来るようにする

<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/88471d6c-eeed-907d-347e-dd216fd6f471.png" alt="スクリーンショット 2024-04-10 3.18.27">

- .normalized
    - 正規化する（各MARKの合計値を100%とする）

<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/423a93db-9417-0770-15bb-906bf935f2ce.png" alt="スクリーンショット 2024-04-10 3.19.47">


- unstacked
    - MarkをStackしない
通常、値が重なっているとMarkが上にstackされ、その2つの合計値が表示される

```Swift
var sales: [Sale] = [
		// 同じ値が2つある場合
    .init(department: "Finance", profit: 200),
    .init(department: "Finance", profit: 200),
    .init(department: "Marketing", profit: 500),
    .init(department: "Production", profit: 150)
]
...
BarMark(
    x: .value("Department", sale.department),
    y: .value("Profit", sale.profit),
    stacking: .standard
)
```

<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/7bc6b890-0309-9df6-d302-55bd5021f451.png" alt="スクリーンショット 2024-05-11 19.07.51">

.unstackedにすることで、上にstackされないようになる
```Swift
BarMark(
    x: .value("Department", sale.department),
    y: .value("Profit", sale.profit),
    stacking: .unstacked
)
```

<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/c631da5a-04e2-43ae-822f-5beb92e4bed1.png" alt="スクリーンショット 2024-05-11 19.11.33">

# MarkDimension
各Markのwidthかheightを表す寸法
BarMarkのイニシャライザで横幅を指定するときなどに使用。
```Swift
BarMark(
    x: .value("Category", $0.category),
    y: .value("value", $0.value),
    width: .fixed(30), // 横幅を30に固定
    stacking: .center // 各Markの中心点が中央に来るように
)
```

- init(floatLiteral: Double)
    - Double型から直接幅を指定する
- init(integerLiteral: Int)
    - Int型から直接幅を指定する
- fixed(CGFloat) → MarkDimension
    - 固定値として幅を指定する
- inset(CGFloat) -> MarkDimension
    - step size（Markごとに割り当てられた間隔）の両側からどの程度間隔を大きくするか
- ratio(CGFloat) → MarkDimension
    - step sizeに対しての比率で幅を指定する

# PlottableValue
chartにplot（表示）するラベル付きのデータのこと。
↓でいう.value()のこと。
```Swift
BarMark(
   x: .value("Category", $0.category), // この.value
   y: .value("Quantity", $0.value)
)
```
時間をデータとして使用する場合、その時間のどの要素を使用するか指定できる

```Swift
.value("Date", item.date, unit: .day)
```
色んな指定方法があるので詳細は[ドキュメント](https://developer.apple.com/documentation/charts/plottablevalue "")を参照

# Plot
複数のMarkを1つにまとめ、複合的なグラフを作成するたのメカニズム。Chart{}を使うと必然的に内部で使っている（？）
ChartとPlotは同じChartContentなので、以下のような記述もできる。
```Swift
struct Chart<Content> where Content : ChartContent

struct Plot<Content> where Content : ChartContent
```

```Swift
Chart {
   Plot {
      BarMark(x: .value("a", 1), y: .value("e", 2))
   }
}
```
おそらく独自のMarkを作成するために使用する

```Swift
struct CustomMark: ChartContent {
    let xValue: PlottableValue<String>
    let yValue: PlottableValue<Double>
    
    var body: some ChartContent {
        Plot {
            LineMark(x: xValue, y: yValue)
            PointMark(x: xValue, y: yValue)
        }
    }
}

...
public var body: some View {
	Chart(sales) {
	    CustomMark(xValue: .value("Department"$0.department), yValue: .value("Profit", $0.profit))
	}
}
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/72fd82b8-04a2-c6c2-20bc-b7e4e77828d6.png" alt="スクリーンショット 2024-05-11 19.32.02">

# 軸のカスタマイズ
ここでは以下のグラフを基に軸をカスタマイズしていく

```Swift
let data: [Sales] = [
    .init(date: date(2023, 1, 1), sales: 4000),
    .init(date: date(2023, 2, 1), sales: 3900),
    .init(date: date(2023, 3, 1), sales: 4100),
    .init(date: date(2023, 4, 1), sales: 4060),
    .init(date: date(2023, 5, 1), sales: 4200),
    .init(date: date(2023, 6, 1), sales: 4400),
    .init(date: date(2023, 7, 1), sales: 4200),
    .init(date: date(2023, 8, 1), sales: 3900),
    .init(date: date(2023, 9, 1), sales: 3960),
    .init(date: date(2023, 10, 1), sales: 4010),
    .init(date: date(2023, 11, 1), sales: 4100),
    .init(date: date(2023, 12, 1), sales: 3000)
]
...
 Chart(data, id: \.date) {
    BarMark(
        x: .value("Month", $0.date, unit: .month),
        y: .value("Sales", $0.sales)
    )
}
.frame(width: 300, height: 300)
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/6639d566-7c61-33e3-d2d7-b06c51525969.png" alt="スクリーンショット 2024-05-11 23.45.28">

## 軸の基本構成
[.chartXAxis、](https://developer.apple.com/documentation/swiftui/view/chartxaxis(content:) "").chartYAxisで各軸のカスタマイズをする。
基本的にAxisMarksを定義し、その中で軸の目盛りやラベルのスタイル、位置などを定義する

```Swift
Chart {
...
}
.chartXAxis {
	AxisMarks {
	    ... // 軸の要素を定義する
	}
}
.chartYAxis {
    AxisMarks() // 引数も何も指定しない場合はデフォルトの状態を提供する
}
```

## AxisMarks
複数のイニシャライザが存在する。ここでは一部だけ掲載。

### AxisMarksのイニシャライザ1

```Swift
init(
    preset: AxisMarkPreset = .automatic, // 目盛りなどに対する軸の位置
    position: AxisMarkPosition = .automatic, // 上下左右での軸の位置
    values: AxisMarkValues = .automatic, // Markをどのように配置するか
    stroke: StrokeStyle? = nil // 線のスタイルを指定
) where Content == Never
```
全てにデフォルト引数があるので↓に該当
```Swift
.chartYAxis {
    AxisMarks {
      ...
    }
    AxisMarks()
}
```

### AxisMarksのイニシャライザ2

```Swift
Chart {
...
}
.chartYAxis {
    AxisMarks(
        format: Decimal.FormatStyle.Percent.percent.scale(1), // %表記
        preset: .aligned, // データに対して整列
        position: .leading, // 軸を左側に配置
        values: .automatic(desiredCount: 3), // 可能であれば3つ配置
        stroke: StrokeStyle(lineWidth: 3) // 線の太さを変更
    )
}
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/1f0d80a9-c8a3-2918-98b2-e89e5d03e920.png" alt="スクリーンショット 2024-05-12 0.19.19">

引数valuesではAxisMarkValues型の値を指定する。
以下の指定が可能

```Swift
.automatic(
   desiredCount: 3, // 目標とするAxisMarkの数。分数とか綺麗な値にならない場合は指定した数値に必ずしもならない
   roundLowerBound: true, // 軸の下限値を丸めるか。trueで0や5など綺麗な値になる
   roundUpperBound: true // 軸の上限値を丸めるか。trueでより綺麗な値になる
)

.automatic(
   minimumStride: 20, // 隣接するAxisMarkとの最小距離。大きくするとMarkの間隔が広がり、軸上のMarkの数が少なる
   desiredCount: 3,
   roundLowerBound: true,
   roundUpperBound: true
)

.stride(
    by: .hour, // 指定したカレンダーの単位に従って値を生成する
    count: 2, // 指定した数の間隔で値を生成する
    roundLowerBound: true,
    roundUpperBound: true,
    calendar: .curren //どのカレンダー（グレゴリオ暦やユリウス暦）に基づくか
)

.stride(
    by: 5, // 指定した間隔で値を生成する
    roundLowerBound: true,
    roundUpperBound: true
)
```

### AxisMarksのイニシャライザ3
表示する目盛りの値を指定できる。

```Swift
AxisMarks(
   values: [0, 1000, 1500, 5000]
)
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/41434340-f376-0f12-43fb-495373bad6bc.png" alt="スクリーンショット 2024-05-12 0.23.54">

## 軸の要素を定義する
AxisMarksに定義するのはラベル、目盛り、グリッド線の3要素。
AxisMarks{}で何も定義してないと軸が空になる

```Swift
Chart {
...
}
.chartXAxis {
	AxisMarks {
	// 空の場合
	}
}
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/b80ce47f-8e17-837e-5c95-5d5b45991a12.png" alt="スクリーンショット 2024-05-12 0.48.07">

### ラベル

```Swift
.chartXAxis {
   AxisMarks {
      AxisValueLabel() // ラベル
   }
}
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/c03b50fb-c9f5-4ada-fb74-28b7bdd36dd2.png" alt="スクリーンショット 2024-05-12 0.49.04">

### 目盛り

```Swift
.chartXAxis {
   AxisMarks {
      AxisValueLabel()
      AxisTick() // 目盛り
   }
}
```
分かりにくいが目盛りはラベルの横に出ている小さい線
![スクリーンショット 2024-05-12 0.51.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/e8a67ed3-462f-0e2c-0b84-3544e17a2d20.png)

### グリッド線

```Swift
.chartXAxis {
   AxisMarks {
      AxisValueLabel()
      AxisTick() 
      AxisGridLine() // グリッド線
   }
}
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/64c25433-0713-6bf6-6a54-3f2443b14560.png" alt="スクリーンショット 2024-05-12 0.55.22">

## 軸の要素の色やフォントを変える
ラベル(AxisValueLabel), 目盛り(AxisTick), グリッド線(AxisGridLine)はAxisMarkプロトコルに準拠しているのでAxisMarks{}で定義できる。
AxisMarkプロトコルでは以下のようなmodifireが提供されている

```Swift
AxisValueLabel() // ラベル
    .font(.title3)
    .foregroundStyle(.red)
    .offset(x: 30)
AxisTick() // 目盛り
    .foregroundStyle(.red)
AxisGridLine() // グリッド線
    .foregroundStyle(.green)
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/30a04ec2-f1b9-e3b1-e00b-34e47da63909.png" alt="スクリーンショット 2024-05-12 1.06.44">

## ラベルのカスタマイズ
AxisValueLabelでは以下の設定が可能

```Swift
AxisValueLabel(
    centered: true, // 軸ラベルが目盛りに対して中心に配置されるか
    anchor: .top, // ラベルのどの部分が指定した位置に固定されるか
    multiLabelAlignment: .bottom, // 複数のラベルが存在する場合の水平方向での整列方法を指定
    collisionResolution: .automatic, // ラベル間の衝突（重なり）を解決する方法を指定
    offsetsMarks: true, // Labelの位置に応じて目盛りをずらすかを指定
    orientation: .horizontal, // Labelの向きを指定。verticalで縦向きになる
    horizontalSpacing: 10, // Label間の水平の距離を指定
    verticalSpacing: 5 // Label間の垂直の距離を指定
)
```

指定した文字列を表示することも可能。データセットにはない文字列を扱う場合に使いそう
```Swift
AxisValueLabel("label")
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/efce9441-5345-98e2-b19c-5c8251bf0b86.png" alt="スクリーンショット 2024-05-12 1.18.45">

### ラベルのFormatの変更
```Swift
// 時(h)で表示
AxisValueLabel(format: .dateTime.hour())
// 少数第二位まで表示
AxisValueLabel(format: Decimal.FormatStyle.number.precision(.fractionLength(2)))
// 四半期スタイルで表示（例： Q1 20223）
AxisValueLabel(format: .dateTime.year().quarter())
```

### Label自体のViewを自分で設定する

```Swift
AxisMarks { value in
    AxisValueLabel {
		    // ここで定義したViewがLabelとして表示される
        if let intValue = value.as(Int.self) {
            HStack(spacing: 0) {
                Text("\(intValue)")
                    .fixedSize()
                Image(systemName: "heart.fill")
            }
            .foregroundStyle(.red)
        }
    }
}
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/ab659d09-2443-dcac-eef0-93c61d67ae9d.png" alt="スクリーンショット 2024-05-12 11.58.43">

## 目盛りのカスタマイズ
AxisTickでは以下の設定が可能
```Swift
AxisTick(
   centered: false, // 目盛りが軸ラベルに対して中心に配置されるか
   length: 20, // 目盛の長さ　（CGFloat型）
   stroke: .init(lineWidth: 5) // 目盛り線のスタイル
)
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/72f41dc7-7e34-b656-9dd6-49368af7d911.png" alt="スクリーンショット 2024-05-12 11.11.31">

lengthはAxisTick.Length型での指定により動的に変更することも可能
```Swift
AxisTick(
   length: .automatic, // 目盛の長さ　（AxisTick.Length型）
)
```
- .automatic
    - 目盛りの長さを自動的に決定する
- .label
    - Labelの幅と同じ長さにする

```Swift
AxisMarks { value in
    if value.index == value.count - 1 {
        AxisValueLabel("長いラベル長いラベル", anchor: .topLeading)
    } else {
        AxisValueLabel(anchor: .topLeading)
    }
    AxisGridLine()
    AxisTick(
        centered: false,
        length: .label, // .labelの幅と同じ長さ
        stroke: .init(lineWidth: 2) // 目盛り線のスタイル
    )
}
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/7e915fbc-298b-1488-a809-5a5edfaa7a78.png" alt="スクリーンショット 2024-05-12 11.23.49">

- .longestLabel
    - 全ての目盛りの長さを、最も長いLabelの幅と同じ長さにする

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/836c1433-b20a-e70a-38f8-2c8ed8f6983a.png" alt="スクリーンショット 2024-05-12 11.25.22">

## グリッド線のカスタマイズ
AxisGridLineでは以下の設定が可能
```Swift
AxisGridLine(
    centered: true, // グリッド線が軸ラベルに対して中心に配置されるか
    stroke: .init(lineWidth: 2, lineCap: .round, dash: [110]) // グリッド線のスタイル
)
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/8ff1f342-1d26-58ab-b04c-90f32daa4273.png" alt="スクリーンショット 2024-05-12 11.34.35">

## データに応じて軸の内容を変える
AxisMarksではクロージャの引数としてAxisValue型のvalueを提供する。
.as()を用いて実際の型にキャストすることで、データの値に応じて軸の内容を変えることができる。

```Swift
AxisMarks { value in
    if let intValue = value.as(Int.self) {
        if intValue > 2000 {
            AxisValueLabel()
                .font(.title)
            AxisGridLine(stroke: .init(lineWidth: 5))
                .foregroundStyle(.red)
        } else {
            AxisValueLabel(orientation: .vertical)
            AxisGridLine()
        }
    }
}
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/a6bbdead-8d7c-49ce-2b2f-a9acfaaf6fca.png" alt="スクリーンショット 2024-05-12 1.29.07">

また、データの総数を取得したり
```Swift
AxisMarks { value in
    if value.count > 5 { // データの総数
         
    }
}
```
データのindexにもアクセスできるので、一番最初や最後の値を強調する時などに使える

```Swift
AxisMarks { value in
    // データのインデックス
    if value.index == value.count - 1 {
        // 一番最後のindexの場合はラベルを赤くする
        AxisValueLabel()
            .foregroundStyle(.red)
        AxisGridLine(stroke: .init(lineWidth: 3, dash: [15, 15]))
    } else {
        AxisValueLabel()
    }
}
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/7b48799c-a6f8-33f0-6044-b919712a7eb2.png" alt="スクリーンショット 2024-05-12 1.42.42">

## 軸のスタイルの設定

```Swift
Chart {
...
}
.chartXAxisStyle { axis in
    axis.opacity(0.5)
}
```

<img width="450" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/7289c968-9cc2-354d-e5ee-6cb4210f2242.png" alt="スクリーンショット 2024-05-26 11.28.38">

## 軸にラベルを追加する

```Swift
Chart {
...
}
.chartXAxisLabel(position: .leading, alignment: .top, spacing: 20) {
    Text("ラベルを追加できる")
        .font(.title)
}
// .chartXAxisLabel("ラベルを追加できる", position: .leading, alignment: .top, spacing: 20) 
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/4e82c918-7336-9eb1-69ac-c104cea4a79e.png" alt="スクリーンショット 2024-05-26 11.49.42">

## データの表示範囲の指定

```Swift
Chart {
...
}
.chartYScale(domain: 0...5000)
```

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/ca924483-045f-2e1f-5d20-23e83586bcf4.png" alt="スクリーンショット 2024-05-11 23.50.12">

## 各AxisMarkに対してそれぞれ処理を行う

```Swift
AxisMarks {
    AxisValueLabel()
    AxisTick()
    AxisGridLine()
}
.compositingLayer() {
    $0.opacity(0.5) // 透明度を0.5
}
```

## 軸、凡例の非表示

```Swift
Chart {
...
}
.chartXAxis(.hidden)
.chartYAxis(.hidden)
.chartLegend(.hidden)
```

## 凡例の位置を指定する
```Swift
.chartLegend(
    position: .top, // 凡例の位置
    alignment: .trailing, // 右側に整列
    spacing: 50 // 凡例とチャートとの幅
)
```

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/b377f0ac-0666-711e-2f2c-5d12b35a3cbb.png" alt="スクリーンショット 2024-05-12 20.58.38">

## 凡例のViewを作る

```Swift
.chartLegend(
    position: .top,
    alignment: .trailing,
    spacing: 10
) {
    HStack(spacing: 0) {
        ForEach(salesByCityData) { citySale in
            Image(systemName: "gearshape")
            Text("\(citySale.city)")
                .font(.caption)
        }
    }
}
```

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/cb55ec22-7067-8409-ca04-825b7be8df47.png" alt="スクリーンショット 2024-05-12 21.04.38">

# グラフをインタラクティブに扱う

## .chartXSelection(value:)
.chartXSelection(value:)を利用することでX軸上のグラフの値を取得し、Bindingすることができる。
全てのジェスチャを認識して処理してくれる。
（iOS17 +）

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/2f18e68d-27c8-4b25-8196-d90185c73c70.png" alt="Simulator Screenshot - iPhone 15 Pro - 2024-05-26 at 01.58.25">

```Swift
// 利益
struct ProfitOverTime {
    var date: Date
    var profit: Double
}

// 利益データ
var data: [ProfitOverTime] {
    [
        ProfitOverTime(date: date(2024, 1, 1), profit: 100),
        ProfitOverTime(date: date(2024, 2, 1), profit: 280),
        ProfitOverTime(date: date(2024, 3, 1), profit: 200),
        ProfitOverTime(date: date(2024, 4, 1), profit: 380),
        ProfitOverTime(date: date(2024, 5, 1), profit: 400),
        ProfitOverTime(date: date(2024, 6, 1), profit: 370),
        ProfitOverTime(date: date(2024, 7, 1), profit: 430),
        ProfitOverTime(date: date(2024, 8, 1), profit: 500),
        ProfitOverTime(date: date(2024, 9, 1), profit: 530),
        ProfitOverTime(date: date(2024, 10, 1), profit: 400),
        ProfitOverTime(date: date(2024, 11, 1), profit: 370),
        ProfitOverTime(date: date(2024, 12, 1), profit: 470),
    ]
}

@State var rawSelectedDate: Date?
var selectedDate: Date? {
    // グラフから受け取った生の値(rawSelectedDate)と、dataの配列の各日付の時間差を比較し、最も近い日付を返す
    guard let rawDate = rawSelectedDate else { return nil }
    let closest = data.min(by: { abs($0.date.timeIntervalSince(rawDate)) < abs($1.date.timeIntervalSince(rawDate)) })
    return closest?.date
}

public var body: some View {
    VStack {
        Chart(data, id: \.date) {
            LineMark(
                x: .value("Date", $0.date, unit: .month),
                y: .value("Profit", $0.profit)
            )
            .interpolationMethod(.catmullRom)
            .symbol(by: .value("departmentName", "A"))
            if let selectedDate {
                // 選択した時にRuleMarkを追加
                RuleMark(
                    x: .value("Selected", selectedDate, unit: .month)
                )
                .foregroundStyle(.gray.opacity(0.3))
                .offset(yStart: -10)
                .zIndex(-1)// RuleMarkがLineMarkの後ろに来るように。defaultは0
                .annotation( // 注釈の作成
                    position: .top, //
                    spacing: 0,
                    overflowResolution: .init(
                        x: .fit(to: .chart), // X軸では注釈がグラフの端の境界を超えないようにfitさせる
                        y: .disabled // Y軸では注釈がグラフのすぐ上に来るようにするようにオーバーフロー解決を無効にする
                    )
                ) {
                    Text(selectedDate, format: Date.FormatStyle(date: .numeric, time: .none))
                        .padding()
                        .background(Color.red)
                }
            }
        }
        .chartXSelection(value: $rawSelectedDate) // タップした位置のX軸上の値を取得
        .frame(height: 300)
        .padding()
        
    }
}
```

## .chartXSelection(range:)
.chartXSelection(range:)を利用することで2本指でタップしている範囲を取得することも可能
（iOS17 +）

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/1827ce9c-d8fc-d8d8-2117-9ed3ae9318ca.jpeg" alt="IMG_3359.jpg">

```Swift
@State var rawSelectedRange: ClosedRange<Date>?
...
Chart {
    LineMark(...)
    ...
    if let rawSelectedRange { // ２本指で選択した範囲を描画する
    RectangleMark(
        xStart: .value("Index", rawSelectedRange.lowerBound),
        xEnd: .value("Index", rawSelectedRange.upperBound)
    )
    .foregroundStyle(.blue.opacity(0.03 ))
}
.chartXSelection(range: $rawSelectedRange) // 2本指でタップした時のrangeも取れる
...
```

## カスタムジェスチャを利用する
`chartGesture`を用いることで、独自のジェスチャに対して選択位置をグラフに通知することができる。
以下の例ではDragGestureを用いてドラッグの開始値と現在値の値を選択状態としてprxyに通知している

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/0c9051ea-09f7-93ee-117a-f7dd79c7acbf.png" alt="Simulator Screenshot - iPhone 15 Pro - 2024-05-26 at 14.28.18">


```Swift
@State private var selectedDay: ClosedRange<Int>?
...
Chart {
    LineMark(
        x: .value("Day", data.day),
        y: .value("Sales", data.sales)
    )
    if let selectedDay {
        RectangleMark(
            xStart: .value("value", selectedDay.lowerBound),
            xEnd: .value("a", selectedDay.upperBound)
        )
    }
}
.chartXScale(domain: 1...7)
.chartXSelection(range: $selectedDay)
.chartGesture { proxy in
    DragGesture(minimumDistance: 0)
        .onChanged { value in
            // ドラッグの開始値と現在値を選択させる
            proxy.selectXRange(from: value.startLocation.x, to: value.predictedEndLocation.x)
            // 範囲ではなくタップしている位置のみを選択する場合
            // proxy.selectXValue(at: value.location.x)
        }
        .onEnded { _ in
            selectedDay = nil
        }
}
```


## .chartAngleSelection(value:)
SectorMarkでは.chartAngleSelection(value:)を利用する

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/dc7a91ef-5883-56bb-0b7c-c623466c758b.png" alt="Simulator Screenshot - iPhone 15 Pro - 2024-05-26 at 02.23.02">

```Swift
struct Food: Identifiable{
    let name: String
    let sales: Int
    var id: String { name }
}

let data: [Food] = [
    .init(name: "Cachapa", sales: 9631),
    .init(name: "Crepe", sales: 6959),
    .init(name: "Injera", sales: 4891),
    .init(name: "Jian Bing", sales: 2506),
    .init(name: "American", sales: 1777),
    .init(name: "Dosa", sales: 625),
]

@State var selectedAngle: Int?
var selectedName: String? {
    guard let selectedAngle else { return nil }
    // 売上の大きい順に並び替え
    let sortedData = data.sorted { $0.sales > $1.sales }
    // 売上を累積してselectedAngleを超えたらその名前を返す
    var accumulatedSales = 0
    for food in sortedData {
        accumulatedSales += food.sales
        if selectedAngle < accumulatedSales {
            return food.name
        }
    }
    return nil
}

var body: some View {
    VStack {
        Chart(data) {
            SectorMark(
                angle: .value("Value", $0.sales),
                innerRadius: .ratio(0.6),
                angularInset: 1
            )
            .foregroundStyle(by: .value("Food Category", $0.name))
            .opacity($0.name == selectedName ? 1.0 : 0.3)
        }
        .chartAngleSelection(value: $selectedAngle) // タップした位置のAngleを取得
        
        .frame(height: 300)
    }
    .padding()
    .chartBackground { chartProxy in
        GeometryReader { geometry in
            let frame = geometry[chartProxy.plotAreaFrame]
            VStack {
                Text("Tapped Food")
                    .font(.callout)
                    .foregroundStyle(.secondary)
                Text(selectedName ?? "nil")
                    .font(.title2.bold())
                    .foregroundStyle(.primary)
            }
            .position(x: frame.midX, y: frame.midY)
        }
    }
}
```

## iOS16の場合
グラフをインタラクティブに扱うには、iOS17では`.chartXSelection(value:)`が利用できるが、iOS16の場合は`.chartOverlay(alignment:content:)`を利用する必要がある。

`.chartOverlay(alignment:content:)`を利用することで、グラフの前面にDragGesture()などを付与したRectangleを配置し、インタラクティブに扱うことが可能となる。
また、`.chartOverlay(alignment:content:)`ではグラフのplotAreaにアクセスするためのProxyを得ることができる。
このProxyを使ってデータ値をスクリーン座標に変換したり、その逆に変換したりできる。

以下はドラッグジェスチャーによってスクリーン座標をデータ値に変換する例。
ドラッグした部分のpriceの値を出力している

```Swift
struct SalesData: Identifiable {
    let id = UUID()
    let year: String
    let price: Int
}

struct ContentView: View {
    let data: [SalesData] = [
        SalesData(year: "2020", price: 200),
        SalesData(year: "2021", price: 400),
        SalesData(year: "2022", price: 600),
        SalesData(year: "2023", price: 800)
    ]
    
    var body: some View {
        Chart(data) {
            LineMark(
                x: .value("year", $0.year),
                y: .value("price", $0.price)
            )
        }
        .chartOverlay { proxy in // proxyを取得
            GeometryReader { geometry in // 座標を取得
            // ドラッグジェスチャーに適応するように構成された透明な長方形のView
                Rectangle().fill(.clear).contentShape(Rectangle())
                    .gesture(
                        DragGesture()
                            .onChanged { value in
                                // ジェスチャーの位置をPlotAreaの座標空間に変換
                                let origin = geometry[proxy.plotFrame!].origin
                                let location = CGPoint(
                                    x: value.location.x - origin.x,
                                    y: value.location.y - origin.y
                                )
                                // 座標からyearとpriceの値を取得する
                                if let (date, price) = proxy.value(at: location, as: (String, Int).self) {
                                    print("Location: \(date), \(price)") 
                                }
                            }
                    )
            }
        }
    }
}
```

以下はドラッグの開始位置から終了位置までの値を出力する例

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237700/24817d3e-f5ba-f6c0-09ee-7dc131fb28f1.png" alt="Simulator Screenshot - iPhone 15 Pro - 2024-05-12 at 19.27.08">


```Swift
// 利益
struct ProfitOverTime {
    var date: Date
    var profit: Double
}

// 利益データ
var data: [ProfitOverTime] {
    [
        ProfitOverTime(date: date(2024, 1, 1), profit: 100),
        ProfitOverTime(date: date(2024, 2, 1), profit: 280),
        ProfitOverTime(date: date(2024, 3, 1), profit: 200),
        ProfitOverTime(date: date(2024, 4, 1), profit: 380),
        ProfitOverTime(date: date(2024, 5, 1), profit: 400),
        ProfitOverTime(date: date(2024, 6, 1), profit: 370),
        ProfitOverTime(date: date(2024, 7, 1), profit: 430),
        ProfitOverTime(date: date(2024, 8, 1), profit: 500),
        ProfitOverTime(date: date(2024, 9, 1), profit: 530),
        ProfitOverTime(date: date(2024, 10, 1), profit: 400),
        ProfitOverTime(date: date(2024, 11, 1), profit: 370),
        ProfitOverTime(date: date(2024, 12, 1), profit: 470),
    ]
}

@State var range: (Date, Date)? = nil
    
public var body: some View {
    VStack {
        Chart(data, id: \.date) {
            LineMark(
                x: .value("Date", $0.date, unit: .month),
                y: .value("Profit", $0.profit)
            )
            .interpolationMethod(.catmullRom)
            .symbol(by: .value("departmentName", "A"))
            if let range = range {
                RectangleMark(
                    xStart: .value("Range Start", range.0),
                    xEnd: .value("Range End", range.1)
                )
                .foregroundStyle(.gray.opacity(0.03))
            }
        }
        .chartOverlay { proxy in
            GeometryReader { geometry in // 座標を取得
                // ドラッグジェスチャーに適応するように構成された長方形のView
                Rectangle().fill(.clear).contentShape(Rectangle())
                    .gesture(
                        DragGesture()
                        .onChanged { value in
                            // plotAreaでドラッグの開始位置と現在の位置のX座標を見つける
                            let startX = value.startLocation.x - geometry[proxy.plotAreaFrame].origin.x
                            let currentX = value.location.x - geometry[proxy.plotAreaFrame].origin.x
                            // 開始と現在のX座標の日付の値を見つける
                            if let startDate: Date = proxy.value(atX: startX),
                                let currentDate: Date = proxy.value(atX: currentX) {
                                // 日付のrangeを渡す
                                range = (startDate, currentDate)
                            }
                        }
                        .onEnded{ _ in
                            range = nil // gestureが終わったらrangeをnilにする
                        }
                    )
            }
        }
        .frame(height: 300)
        ForEach(data, id: \.date) { value in
            if let range = range, value.date > range.0, value.date < range.1 {
                Text(value.date, format: .dateTime)
            }
        }
    }
    .frame(maxHeight: .infinity, alignment: .top)
}
```

## グラフをスクロール可能にする
指定した方向にスクロール可能にする

```Swift
Chart {
   BarMark(...)
}
.chartScrollableAxes(.horizontal) // スクロールを可能にする
```

### スクロール可能なグラフに表示する範囲を指定する

```Swift
Chart {
   BarMark(...)
}
.chartScrollableAxes(.horizontal)
.chartXVisibleDomain(length: 10) // 表示範囲を10までにする(X軸が1,2,3,4...となっている場合)
//.chartXVisibleDomain(length: 3600 * 6) // 表示範囲を6時間分にする（X軸が時間の場合）
```

### スクロールの位置をbindingさせる

```Swift
@State var scrollPosition: Date = Date() // 現在時刻
...
Chart {
   BarMark(...)
}
.chartScrollableAxes(.horizontal)
.chartXVisibleDomain(length: 3600 * 6)
.chartScrollPosition(x: $scrollPosition) // X軸の現在時刻の値までスクロールさせた状態にする
```

### スクロールする位置を調整
`chartScrollTargetBehavior`を用いることでスクロール終了時の位置を指定することができる。
以下の例ではスクロール終了時に最も近いX軸のデータのいずれかの位置に調整される

```Swift
struct SalesData: Identifiable {
    let id = UUID()
    let day: Int
    let sales: Int
}

let salesData: [SalesData] = [
    SalesData(day: 1, sales: 100),
    SalesData(day: 2, sales: 150),
    SalesData(day: 3, sales: 200),
    SalesData(day: 4, sales: 250),
    SalesData(day: 5, sales: 300),
    SalesData(day: 6, sales: 350),
    SalesData(day: 7, sales: 400),
    SalesData(day: 8, sales: 450),
    SalesData(day: 9, sales: 500),
    SalesData(day: 10, sales: 550),
    SalesData(day: 11, sales: 600),
    SalesData(day: 12, sales: 650)
]

var body: some View {
    Chart(salesData) { data in
      LineMark(
         x: .value("Day", data.day),
         y: .value("Sales", data.sales)
      )
    }
    .chartScrollableAxes(.horizontal)
    .chartXVisibleDomain(length: 3)
    .chartScrollTargetBehavior(.valueAligned(unit: 1)) // スクロール終了時にX軸のデータの位置になるように調整する
    .frame(height: 300)
    .padding()
}
```

# Chartのデザイン

[HIGにChartsについての記載](https://developer.apple.com/design/human-interface-guidelines/charts)がある。

WWDC22の以下2つはグラフのデザインについてのセッションで実装する際の参考になる。両方ともコードは一切出てこないので非エンジニアでも見れそう。

- [Design app experiences with charts](https://developer.apple.com/videos/play/wwdc2022/110342)
- [Design an effective chart](https://developer.apple.com/videos/play/wwdc2022/110340)


# おわりに
多分Chartsについてまとめてる記事の中で一番細かいところまで書いてるんじゃないかなと思います。
冒頭でも触れたようにドキュメントの内容全てを網羅してるわけでもないので（現時点で8割ほど）、随時更新します。
また、間違いなどもあると思うのでコメントいただけると助かります。

# 参考資料

https://developer.apple.com/documentation/charts/creating-a-chart-using-swift-charts

https://developer.apple.com/documentation/swiftui/view-chart-view

WWDC22
- [Hello Swift Charts](https://developer.apple.com/videos/play/wwdc2022/10136/)
- [Swift Charts: Raise the bar](https://developer.apple.com/videos/play/wwdc2022/10137)
- [Design an effective chart](https://developer.apple.com/videos/play/wwdc2022/110340)
- [Design app experiences with charts](https://developer.apple.com/videos/play/wwdc2022/110342)

WWDC23
- [Explore pie charts and interactivity in Swift Charts](https://developer.apple.com/videos/play/wwdc2023/10037/)