###### 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)