###### tags: `第14屆IT邦鐵人賽文章`
# 【在 iOS 開發路上的大小事2-Day27】來自 Apple 爸爸的最新力作 - Swift Charts 之 RectangleMark 實作篇
上一篇介紹了 PointMark 的實作,今天要來介紹的是 Swift Charts 的 RectangleMark
RectangleMark 一共提供了九種 init 的方法,讓開發者可以繪製不同樣式的圖表
```swift
public init<X, Y>(x: PlottableValue<X>,
y: PlottableValue<Y>,
width: MarkDimension = .automatic,
height: MarkDimension = .automatic) where X : Plottable, Y : Plottable
public init<X>(x: PlottableValue<X>,
yStart: CGFloat? = nil,
yEnd: CGFloat? = nil,
width: MarkDimension = .automatic) where X : Plottable
public init<Y>(xStart: CGFloat? = nil,
xEnd: CGFloat? = nil,
y: PlottableValue<Y>,
height: MarkDimension = .automatic) where Y : Plottable
public init<X, Y>(xStart: PlottableValue<X>,
xEnd: PlottableValue<X>,
y: PlottableValue<Y>,
height: MarkDimension = .automatic) where X : Plottable, Y : Plottable
public init<X>(xStart: PlottableValue<X>,
xEnd: PlottableValue<X>,
yStart: CGFloat? = nil,
yEnd: CGFloat? = nil) where X : Plottable
public init<X, Y>(x: PlottableValue<X>,
yStart: PlottableValue<Y>,
yEnd: PlottableValue<Y>,
width: MarkDimension = .automatic) where X : Plottable, Y : Plottable
public init<Y>(xStart: CGFloat? = nil,
xEnd: CGFloat? = nil,
yStart: PlottableValue<Y>,
yEnd: PlottableValue<Y>) where Y : Plottable
public init<X, Y>(xStart: PlottableValue<X>,
xEnd: PlottableValue<X>,
yStart: PlottableValue<Y>,
yEnd: PlottableValue<Y>) where X : Plottable, Y : Plottable
public init(xStart: CGFloat? = nil,
xEnd: CGFloat? = nil,
yStart: CGFloat? = nil,
yEnd: CGFloat? = nil)
```
由於我想不太到 RectangleMark 可以用什麼方式來呈現
所以我們就來參考一下 Apple 的官方範例吧~
## Apple 官方範例
![Create a Heat Map with Rectangle Marks](https://i.imgur.com/yuMTutw.png)
> When presenting data about the effectiveness of a machine learning model, you typically organize the data using a confusion matrix which shows the predicted versus the actual results of the model. To create a 2D heat map that represents a machine learning model you use init(x:y:width:height:).
中文翻譯如下:
> 在呈現有關機器學習模型有效性的數據時,您通常使用混淆矩陣來組織數據,該矩陣顯示模型的預測結果與實際結果。 要創建表示機器學習模型的 2D 熱圖,請使用 init(x:y:width:height:)。
### Model
```swift
import SwiftUI
struct MatrixEntry: Identifiable {
var id = UUID().uuidString
var positive: String
var negative: String
var num: Double
}
```
### ViewModel
```swift
import SwiftUI
class MatrixEntryViewModel {
var matrixData: [MatrixEntry] = [
.init(positive: "+", negative: "+", num: Double.random(in: 1 ... 200)),
.init(positive: "+", negative: "-", num: Double.random(in: 1 ... 200)),
.init(positive: "-", negative: "-", num: Double.random(in: 1 ... 200)),
.init(positive: "-", negative: "+", num: Double.random(in: 1 ... 200))
]
}
```
### View
這邊要記得 import Charts,因為我們要顯示 RectangleMark 在畫面上
然後這邊宣告了一個 ViewModel 的變數 vm
並在前面加上 @State 修飾字,讓 SwiftUI 來幫我們管理 ViewModel 狀態
接著是 Charts 的語法,語法也是很簡單,像是下面這樣
```swift
@State private var vm = MatrixEntityViewModel()
// 1:vm.matrixData,圖表的資料來源
Chart(vm.matrixData) {
RectangleMark(
x: .value("Positive", $0.positive), // 2:x 軸要顯示的資料
y: .value("Negative", $0.negative) // 3:y 軸要顯示的資料
)
.foregroundStyle(by: .value("Number", $0.num)) // 4:左下角的圖例樣式
}
.frame(height: 300)
.padding()
```
或者你也可以透過 ForEach 來寫,只是就會要讓 Model 繼承 Identifiable
並宣告 UUID() 變數在 Model 裡面,像是這樣 var id = UUID().uuidString
```swift
@State private var vm = MatrixEntityViewModel()
Chart {
// 1:vm.matrixData,圖表的資料來源
ForEach(vm.matrixData) { matrix in
RectangleMark(
x: .value("Positive", matrix.positive), // 2:x 軸要顯示的資料
y: .value("Negative", matrix.negative) // 3:y 軸要顯示的資料
)
.foregroundStyle(by: .value("Number", matrix.num)) // 4:左下角的圖例樣式
}
}
.frame(height: 300)
.padding()
```
現在的圖,應該會長得像下面這樣
![Heat Map](https://i.imgur.com/7uBxMCI.png)
接著,Apple 還有示範用 PointMark 搭配這篇實作的 RectangleMark
## Apple 官方範例 2
![Annotate a Rectangular Area with Rectangle Marks](https://i.imgur.com/OYhzxhq.png)
讓我們一起來實作一下吧
### Model
```swift
import SwiftUI
struct Coord: Identifiable {
var id = UUID().uuidString
var x: Double
var y: Double
}
```
### ViewModel
```swift
import SwiftUI
class CoordViewModel {
var coordData: [Coord] = [
.init(x: 5, y: 5),
.init(x: 2.5, y: 2.5),
.init(x: 3, y: 3)
]
}
```
### View
```swift
@State private var vm = CoordViewModel()
Chart {
ForEach(vm.coordData) { coord in
RectangleMark(
xStart: .value("Rect Start Width", coord.x - 0.25),
xEnd: .value("Rect End Width", coord.x + 0.25),
yStart: .value("Rect Start Height", coord.y - 0.25),
yEnd: .value("Rect End Height", coord.y + 0.25)
)
.opacity(0.2)
PointMark(
x: .value("X", coord.x),
y: .value("Y", coord.y)
)
}
}
.frame(height: 300)
.padding()
```
出來的畫面,會長得像下面這樣
![Annotate a Rectangular Area with Rectangle Marks](https://i.imgur.com/aCh2Lhx.png)
## 完整程式碼 (RectangleMarkView)
```swift
import SwiftUI
import Charts
struct RectangleMarkView: View {
@State private var vm = MatrixEntryViewModel()
var body: some View {
Chart {
ForEach(vm.matrixData) { matrix in
RectangleMark(
x: .value("Positive", matrix.positive),
y: .value("Negative", matrix.negative)
)
.foregroundStyle(by: .value("Number", matrix.num))
}
}
.frame(height: 300)
.padding()
}
}
struct RectangleMarkView_Previews: PreviewProvider {
static var previews: some View {
RectangleMarkView()
}
}
```
## 完整程式碼 (RectangleMark + PointMark)
```swift
import SwiftUI
import Charts
struct RectangleMark_PointMarkView: View {
@State private var vm = CoordViewModel()
var body: some View {
Chart {
ForEach(vm.coordData) { coord in
RectangleMark(
xStart: .value("Rect Start Width", coord.x - 0.25),
xEnd: .value("Rect End Width", coord.x + 0.25),
yStart: .value("Rect Start Height", coord.y - 0.25),
yEnd: .value("Rect End Height", coord.y + 0.25)
)
.opacity(0.2)
PointMark(
x: .value("X", coord.x),
y: .value("Y", coord.y)
)
}
}
.frame(height: 300)
.padding()
}
}
struct RectangleMark_PointMarkView_Previews: PreviewProvider {
static var previews: some View {
RectangleMark_PointMarkView()
}
}
```
## 總結
這篇依照 Apple 官方教學簡單實作了 Swift Charts 中的 RectangleMark
明天會來介紹 Swift Charts 中的 RuleMark,讓我們繼續看下去吧~
## 參考資料
> 1. [https://developer.apple.com/documentation/charts/rectanglemark](https://developer.apple.com/documentation/charts/rectanglemark)