上一篇介紹了 RuleMark 的實作,今天要來介紹的是 Swift Charts 系列中的最後一個圖表 BarMark
BarMark 一共提供了七種 init 的方法,讓開發者可以繪製不同樣式的圖表
public init<X, Y>(x: PlottableValue<X>,
y: PlottableValue<Y>,
width: MarkDimension = .automatic,
height: MarkDimension = .automatic,
stacking: MarkStackingMethod = .standard) where X : Plottable, Y : Plottable
public init<X>(x: PlottableValue<X>,
yStart: CGFloat? = nil,
yEnd: CGFloat? = nil,
width: MarkDimension = .automatic,
stacking: MarkStackingMethod = .standard) where X : Plottable
public init<Y>(xStart: CGFloat? = nil,
xEnd: CGFloat? = nil,
y: PlottableValue<Y>,
height: MarkDimension = .automatic,
stacking: MarkStackingMethod = .standard) 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
import SwiftUI
struct DepartmentEntity: Identifiable {
var id = UUID().uuidString
var department: String
var profit: Int
}
import SwiftUI
class DepartmentEntityViewModel {
var departmentData: [DepartmentEntity] = [
.init(department: "Production", profit: 15000),
.init(department: "Marketing", profit: 8000),
.init(department: "Finance", profit: 10000)
]
}
這邊要記得 import Charts
,因為我們要顯示 BarMark 在畫面上
然後這邊宣告了一個 ViewModel 的變數 deVM
並在前面加上 @State
修飾字,讓 SwiftUI 來幫我們管理 ViewModel 狀態
接著是 Charts 的語法,語法也是很簡單,像是下面這樣
@State private var deVM = DepartmentEntityViewModel()
// 1:deVM.departmentData,圖表的資料來源
Chart(deVM.departmentData) {
BarMark(
x: .value("Department", $0.department), // 2:x 軸要顯示的資料
y: .value("Profit", $0.profit) // 3:y 軸要顯示的資料
)
}
.frame(height: 300)
.padding()
或者你也可以透過 ForEach 來寫,只是就會要讓 Model 繼承 Identifiable
並宣告 UUID() 變數在 Model 裡面,像是這樣 var id = UUID().uuidString
@State private var deVM = DepartmentEntityViewModel()
Chart {
// 1:deVM.departmentData,圖表的資料來源
ForEach(deVM.departmentData) { department in
BarMark(
x: .value("Department", department.department), // 2:x 軸要顯示的資料
y: .value("Profit", department.profit) // 3:y 軸要顯示的資料
)
}
}
.frame(height: 300)
.padding()
現在的圖,應該會長得像下面這樣
如果要將每個 Bar 都顯示對應數值的話,可以透過 .annotation
這個 modifier
@State private var deVM = DepartmentEntityViewModel()
Chart {
ForEach(deVM.departmentData) { department in
BarMark(
x: .value("Department", department.department),
y: .value("Profit", department.profit)
)
.annotation {
Text("\(department.profit)")
}
}
}
.chartYAxisLabel("Normal", alignment: .center)
.frame(height: 300)
.padding()
加完後,會長得像下面這樣
接下來還有像是堆疊樣式的 BarMark
讓我們先來改寫一下
import SwiftUI
struct DepartmentCategoryEntity: Identifiable {
var id = UUID().uuidString
var department: String
var profit: Double
var category: String
}
import SwiftUI
class DepartmentCategoryEntityViewModel {
var departmentData: [DepartmentCategoryEntity] = [
.init(department: "Production", profit: 4000, category: "Gizmos"),
.init(department: "Production", profit: 5000, category: "Gadgets"),
.init(department: "Production", profit: 6000, category: "Widgets"),
.init(department: "Marketing", profit: 2000, category: "Gizmos"),
.init(department: "Marketing", profit: 1000, category: "Gadgets"),
.init(department: "Marketing", profit: 5000, category: "Widgets"),
.init(department: "Finance", profit: 2000, category: "Gizmos"),
.init(department: "Finance", profit: 3000, category: "Gadgets"),
.init(department: "Finance", profit: 5000, category: "Widgets")
]
}
@State private var dceVM = DepartmentCategoryEntityViewModel()
Chart {
ForEach(dceVM.departmentData) { department in
BarMark(
x: .value("Category", department.department),
y: .value("Profit", department.profit),
stacking: .standard
)
.foregroundStyle(by: .value("Product Category", department.category))
}
}
.chartYAxisLabel("Stacking.standard", alignment: .center)
.frame(height: 300)
.padding()
這邊有一個 optional 參數 stacking:
可以改變 BarMark 的堆疊樣式
stacking 樣式一共有四種,standard (預設值)
、normalized
、center
、unstacked
可以依照自己的需求,來改變 BarMark 的顯示方式
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
@frozen public struct MarkStackingMethod : Equatable {
/// Stack marks starting at zero.
///
/// Negative values appear below zero, creating diverging stacked marks.
@inlinable public static var standard: MarkStackingMethod { get }
/// Create normalized stacked bar and area charts.
@inlinable public static var normalized: MarkStackingMethod { get }
/// Stack marks using a center offset.
///
/// Use this type to create a stream graph.
@inlinable public static var center: MarkStackingMethod { get }
/// Don't stack marks.
@inlinable public static var unstacked: MarkStackingMethod { get }
}
在 手機設定 -> 一般 -> iPhone 儲存空間 裡面,會看到最上面有一條柱狀圖
那我們要如何繪製一個類似的圖表呢?這時候就可以透過 BarMark 不同的 init 來做到
public init<X>(x: PlottableValue<X>,
yStart: CGFloat? = nil,
yEnd: CGFloat? = nil,
width: MarkDimension = .automatic,
stacking: MarkStackingMethod = .standard) where X : Plottable
import SwiftUI
struct FileCategoryEntity: Identifiable {
var id = UUID().uuidString
var fileSizePercent: Double
var fileCategory: String
}
import SwiftUI
class FileCategoryEntityViewModel {
var fileData: [FileCategoryEntity] = [
.init(fileSizePercent: 20, fileCategory: "App"),
.init(fileSizePercent: 40, fileCategory: "照片"),
.init(fileSizePercent: 5, fileCategory: "媒體"),
.init(fileSizePercent: 10, fileCategory: "訊息"),
.init(fileSizePercent: 12, fileCategory: "iOS"),
.init(fileSizePercent: 13, fileCategory: "系統資料"),
]
}
@State private var vm = FileCategoryEntityViewModel()
Chart {
ForEach(vm.fileData) { file in
BarMark(
x: .value("File Size Percent", file.fileSizePercent)
)
.foregroundStyle(by: .value("File Category", file.fileCategory))
}
}
.chartXAxis(.hidden)
.frame(height: 100)
.padding()
現在的圖,應該會長得像下面這樣
import SwiftUI
import Charts
struct BarMarkView: View {
@State private var deVM = DepartmentEntityViewModel()
var body: some View {
Chart {
ForEach(deVM.departmentData) { department in
BarMark(
x: .value("Department", department.department),
y: .value("Profit", department.profit)
)
.annotation {
Text("\(department.profit)")
}
}
}
.chartYAxisLabel("Normal", alignment: .center)
.frame(height: 300)
.padding()
}
}
struct BarChartView_Previews: PreviewProvider {
static var previews: some View {
BarMarkView()
}
}
import SwiftUI
import Charts
struct BarMarkView: View {
@State private var dceVM = DepartmentCategoryEntityViewModel()
var body: some View {
Chart {
ForEach(dceVM.departmentData) { department in
BarMark(
x: .value("Category", department.department),
y: .value("Profit", department.profit),
stacking: .standard
)
.foregroundStyle(by: .value("Product Category", department.category))
}
}
.chartYAxisLabel("Stacking.standard", alignment: .center)
.frame(height: 300)
.padding()
}
}
struct BarChartView_Previews: PreviewProvider {
static var previews: some View {
BarMarkView()
}
}
import SwiftUI
import Charts
struct OneDBarMarkView: View {
@State private var vm = FileCategoryEntityViewModel()
var body: some View {
Chart {
ForEach(vm.fileData) { file in
BarMark(
x: .value("File Size Percent", file.fileSizePercent)
)
.foregroundStyle(by: .value("File Category", file.fileCategory))
}
}
.chartXAxis(.hidden)
.frame(height: 100)
.padding()
}
}
struct OneDBarMarkView_Previews: PreviewProvider {
static var previews: some View {
OneDBarMarkView()
}
}
這篇簡單實作了 Swift Charts 中的 BarMark
在這幾篇的 Swift Charts 實作中,我個人覺得 Swift Charts 算是滿容易上手的,功能也算多
唯一美中不足的部分可能就是只支援 SwiftUI,但現在 UIKit 也可以透過 UIHostingController 來串接 SwiftUI 的畫面,所以說還可以啦?
期待之後 Apple 為 Swift Charts 加入更多可玩性!
在這幾篇所實作的 Swift Charts 的完整程式碼,可以到我的 GitHub 上找到喔~