WWDC22: Swift Charts 初探
发布
更新
字数
1418
阅读
8 分钟
阅读量
1346
Swift Charts 是由 Apple 提供的新框架,用来展示数据图表信息,基于 SwiftUI 实现。
Marks
Swift Charts 基本视觉元素被称之为 marks
,例如柱状图的柱等。
第一个例子
销售情况
import Charts
import SwiftUI
struct StlesDetailsChart: View {
var body: some View {
Chart {
BarMark(
// `.value` 工厂方法
// 第一个参数是标签描述
// 第二个则是标签值
// 所以此处,x 轴按名称分组
x: .value("Name", "Cachapa"),
// y 值
// 此处使用交易量
// `.value` 方法会自动根据坐标系
// 计算 bar 元素的高度,并加入等高线
y: .value("Sales", 916)
)
// 第二个 bar
// 多个 `marks` 会自动布局
BarMark(
x: .value("Name", "Injera"),
y: .value("Sales", 850)
)
}
}
}
注意
.value
方法的第一个参数,这里都是一致的,试一下把x
值改成不一样的 label name 会有什么效果。
更多数据
通常我们的数据都是来自一个结合,如数组,所以我们需要更新一下我们的数据结构和显示图表的逻辑。接下来,我们会:
- 创建一个
Pancakges
结构体用来描述其销售情况 - 然后我们就可以得到一个销售列表:一个包含
Pancakges
的数组 - 最后我们会用
ForEach
循环创建柱状图来对比不同 pancake 的销售情况
import Charts
import SwiftUI
// 1. Pancakes 结构体
// 我们需要在 for 循环中使用,所以需要遵循 `Identifiable` 协议
struct Pancakges: Identifiable {
// 这里的 name 和 sales 对应前面 `BarMark` 的 x, y label name
let name: String
let sales: Int
// `Identifiable`
// 直接使用 name 即可
var id: String { name }
}
// 2. 销售列表
let sales: [Pancakes] = [
.init(name: "Cachapa", salse: 916),
.init(name: "Injera", salse: 850),
.init(name: "Crêpe", salse: 802),
]
struct StlesDetailsChart: View {
var body: some View {
Chart {
ForEach(sales) { element in
BarMark(
x: .value("Name", element.name),
y: .value("Sales", element.sales)
)
}
}
}
}
之后我们可以添加更多的数据
let sales: [Pancakes] = [
.init(name: "Cachapa", salse: 916),
.init(name: "Injera", salse: 850),
.init(name: "Crêpe", salse: 802),
.init(name: "Jian Bing", salse: 753),
.init(name: "Dosa", salse: 654),
.init(name: "American", salse: 618),
]
然后交换一下 x y 的值
BarMark(
x: .value("Sales", element.sales),
y: .value("Name", element.name)
)
可以看到柱状图横过来了。为了达到最美观的效果,Swift Charts 会自动选择合适的坐标系风格。同时自动适配暗黑模式、系统字体大小、设备尺寸、方向以及可用性支持,如 VoiceOver 支持。
什么时候去哪里更好卖?
为了找出一周不同时间的最佳售卖地点,我们需要一个新的图表可视化的呈现出,不同城市在同一时间的销售对比情况,所以需要以下步骤:
- 定义一个销售摘要结构体,描述不同日期的销售情况
- 找出不同城市一周的销售数据
- 创建图表
import Charts
import SwiftUI
// 1. 销售摘要
struct SalesSummary: Identifiable {
let weekday: Date
let sales: Int
var id: Date { weekday }
}
// 2. 数据集
let cupertinoData: [SalesSummary] = [
/// Monday
.init(weekday: date(2022, 5, 2), sales: 54),
/// Tuesday
.init(weekday: date(2022, 5, 3), sales: 42),
/// Wednesday
.init(weekday: date(2022, 5, 4), sales: 88),
/// Thursday
.init(weekday: date(2022, 5, 5), sales: 49),
/// Friday
.init(weekday: date(2022, 5, 6), sales: 42),
/// Saturday
.init(weekday: date(2022, 5, 7), sales: 125),
/// Sunday
.init(weekday: date(2022, 5, 8), sales: 67),
]
// 3. 可视化
struct CupertinoDetailsChart: View {
var body: some View {
// 把数据集作为参数传入,循环应用 element
Chart(cupertinoData) { element in
BarMark(
// x 周显示日期,可以指定 `unit` 参数
x: .value("Day", element.weekday, unit: .day),
// 销售情况
y: .value("Sales", element.sales)
)
}
}
}
为了能在不同城市间切换查看销售数据,我们需要优化一下显示的逻辑,使得其可以根据数据集的变化而调整图表。所以这里会:
- 增加一个新的城市数据集作为示例
- 创建一个
State
变量,用来保存当前查看的城市
// 1. 增加数据集
let sfData: [SalesSummary] = [
.init(weekday: date(2022, 5, 2), sales: 81),
.init(weekday: date(2022, 5, 3), sales: 90),
.init(weekday: date(2022, 5, 4), sales: 52),
.init(weekday: date(2022, 5, 5), sales: 72),
.init(weekday: date(2022, 5, 6), sales: 84),
.init(weekday: date(2022, 5, 7), sales: 84),
.init(weekday: date(2022, 5, 8), sales: 137),
]
struct LocationsToggleChart: View {
// 2. 城市变量
@State var city: City = .cupertino
var data: [SalesSummary] {
switch city {
case .cupertino:
return cupertinoData
case .sanFrancisco:
return sfData
}
}
var body: some View {
Vstack {
// 选择器,切换城市,同时触发动画效果
Picker("City", selection: $city.animation(.easeInOut)) {
Text("Cupertino").tag(City.cupertino)
Text("San Francisco").tag(City.sanFrancisco)
}
.pickerStyle(.segmented)
// 图表
Chart(data) {element in
BarMark(
// x 周显示日期,可以指定 `unit` 参数
x: .value("Day", element.weekday, unit: .day),
// 销售情况
y: .value("Sales", element.sales)
)
}
}
}
注意这里通过
State
变量添加修饰符的方式来创建动画.animation(.easeInOut)
再加一层
为了更直观的对比两个城市的销售情况,我们把两个城市的数据放在一起绘制,对比查看一周内的销售趋势。
- 定义一个 Series 结构体,区分不同城市的销售数据
- 定义一个 Series 数据集,用来绘制图表
struct Series: Identifiable {
let city: String
let sales: [SalesSummary]
var id: String { city }
}
let seriesData: [Series] = [
.init(city: "Cupertino", sales: cupertinoData),
.init(city: "San Francisco", sales: sfData),
]
struct LocationsDetailsChart: View {
var body: some View {
Chart(seriesData) { series in
ForEach(series.sales) { element in
BarMark(
x: .value("Day", element.weekday, unit: .day),
y: .value("Sales", element.sales)
)
// 用颜色区分不同城市
.foregroundStyle(by: .value("City", series.city)
}
}
}
}
试着把 BarMark
换为 PointMark
或 LineChart
可以得到不同的效果
struct LocationsDetailsChart: View {
var body: some View {
Chart(seriesData) { series in
ForEach(series.sales) { element in
LineMark(
x: .value("Day", element.weekday, unit: .day),
y: .value("Sales", element.sales)
)
.foregroundStyle(by: .value("City", series.city)
PointMark(
x: .value("Day", element.weekday, unit: .day),
y: .value("Sales", element.sales)
)
.foregroundStyle(by: .value("City", series.city)
.symbol(by: .value("City", series.city)
}
}
}
}
本文根据 WWDC22 Hello Swift Charts 编写:https://developer.apple.com/videos/play/wwdc2022/10136/