1、前言
Swift提供了Charts框架,使得我们可以通过简单的代码即可在iOS、iPad、Mac和Apple watch等设备中显示图表,并且支持自定义,轻易实现各种风格的图表。这一篇我们继续关注Apple watch,将数据以图表的形式显示在Apple Watch中。例如这是我最近做的项目截图,以折线图显示用户当天Hrv数据的变化:
这是苹果官方App中使用Charts的示例图:
2、创建项目
同样的这系列文章主要介绍Apple watch开发,所以本章示例也只创建watch app:
项目名为ChartsDemo,勾选Watch-only App:
3、显示柱状图
每一个图表,为一个Chart组件,如果是柱状图,每一柱形为一个BarMark组件,例如某奶茶店有广州和深圳两家店铺,要用组状图显示某天的销售量,完整示例代码如下:
import SwiftUI
import Charts
struct ContentView: View {
var body: some View {
Chart() {
BarMark(
x: .value("销量", 916),
y: .value("地区", "广州")
)
BarMark(
x: .value("销量", 1190),
y: .value("地区", "深圳")
)
}
}
}
#Preview {
ContentView()
}
框架会默认会根据数据,给我们设置xy轴的范围。
在日常开发中,一般都是数据驱动视图,所以示例中的核心代码应该为:
let data = [
(city:"广州",sales:916),
(city:"深圳",sales:1190),
(city:"北京",sales:1890),
(city:"上海",sales:1497)
]
var body: some View {
Chart() {
ForEach(data,id: \.city) {
BarMark(
x: .value("销量", $0.sales),
y: .value("城市", $0.city)
)
}
}
}
我们可以进一步简化上面的代码:
var body: some View {
Chart(data,id: \.city) {
BarMark(
x: .value("销量", $0.sales),
y: .value("城市", $0.city)
)
}
}
注意:如果有两条city名称一样的数据,框架会自动合并成一条数据
3、显示折线图
先上代码和运行效果:
import SwiftUI
import Charts
struct ContentView: View {
let data = [
(day:Util.getDate(offset:1),sales:1666),
(day:Util.getDate(offset:2),sales:1899),
(day:Util.getDate(offset:3),sales:1254),
(day:Util.getDate(offset:4),sales:1200),
(day:Util.getDate(offset:5),sales:983),
(day:Util.getDate(offset:6),sales:1101),
(day:Util.getDate(offset:7),sales:801),
]
var body: some View {
Chart(data,id: \.day) {
BarMark(
x: .value("日期", $0.day,unit: .day),
y: .value("销售量", $0.sales)
)
.foregroundStyle(.pink)
}
}
}
class Util {
static func getDate(offset:Int) -> Date {
let calendar = Calendar.current
return calendar.date(byAdding: .day, value: -offset, to: Date()) ?? Date()
}
}
#Preview {
ContentView()
}
如上,我们先实现了最近一周销量柱状图,创建了Util工具类用于获取过去某天的日期,然后以日期升序为x坐标,日销售量为y坐标绘制出了对应的柱状图。
我们现在想要以折线图形式展示数据,只需一个小改动,将上面代码中的BarMark改为LineeMark即可,核心代码块:
var body: some View {
Chart(data,id: \.day) {
LineMark(
x: .value("日期", $0.day,unit: .day),
y: .value("销售量", $0.sales)
)
.foregroundStyle(.pink)
}
}
预览效果:
我们还可以在同一个图表中,显示广州、深圳两个城市最近一周的销售对比折线图,核心代码如下:
struct ContentView: View {
var body: some View {
let gzData:[(day:Date,sales:Int)] = [
(day:Util.getDate(offset:1),sales:1666),
(day:Util.getDate(offset:2),sales:1899),
(day:Util.getDate(offset:3),sales:1254),
(day:Util.getDate(offset:4),sales:1200),
(day:Util.getDate(offset:5),sales:983),
(day:Util.getDate(offset:6),sales:1101),
(day:Util.getDate(offset:7),sales:801),
]
let szData:[(day:Date,sales:Int)] = [
(day:Util.getDate(offset:1),sales:2287),
(day:Util.getDate(offset:2),sales:1655),
(day:Util.getDate(offset:3),sales:1598),
(day:Util.getDate(offset:4),sales:1067),
(day:Util.getDate(offset:5),sales:900),
(day:Util.getDate(offset:6),sales:1201),
(day:Util.getDate(offset:7),sales:540),
]
let sericeData = [
(city:"广州",data: gzData),
(city:"深圳",data: szData)
]
Chart {
ForEach(sericeData,id: \.city) { serice in
ForEach(serice.data,id: \.day) {
LineMark(
x: .value("日期", $0.day,unit: .day),
y: .value("销售量", $0.sales)
)
}
.foregroundStyle(by: .value("City", serice.city))
}
}
}
}
预览效果:
其中代码
.foregroundStyle(by: .value("City", serice.city))
表示我们需要不同城市以不同颜色进行区分。同样的,我们还可以设置折线为平滑曲线,只需添加以下一行配置:
.interpolationMethod(.catmullRom)
预览效果:
为了使数据更明显,也可以给线条加上符号:
.symbol(.circle)
预览效果:
尝试将LineMark改回BarMark,看下是什么效果?
再添加.position(by: .value("City", serice.city)),看下是什么效果?
4、其它类型
除了上面介绍了的柱状图和折线图,Charts还支持其它标记类型:
甚至能将他们相互组合,构建更为复杂的图表。如下图,使用LineMark显示某家店最近一年每个月的日均销售量,同时通过AreaMark显示当月最高和最低销售量情况:
完整代码如下:
import SwiftUI
import Charts
struct ContentView: View {
var body: some View {
let data = [
(month:Util.getMonth(offset:1),dailyAverage:939,dailyMin: 550,daiyMax: 1209),
(month:Util.getMonth(offset:2),dailyAverage:879,dailyMin: 399,daiyMax: 1127),
(month:Util.getMonth(offset:3),dailyAverage:840,dailyMin: 422,daiyMax: 1009),
(month:Util.getMonth(offset:4),dailyAverage:823,dailyMin: 439,daiyMax: 991),
(month:Util.getMonth(offset:5),dailyAverage:797,dailyMin: 378,daiyMax: 965),
(month:Util.getMonth(offset:6),dailyAverage:800,dailyMin: 380,daiyMax: 891),
(month:Util.getMonth(offset:7),dailyAverage:820,dailyMin: 401,daiyMax: 911),
(month:Util.getMonth(offset:8),dailyAverage:832,dailyMin: 390,daiyMax: 1001),
(month:Util.getMonth(offset:9),dailyAverage:791,dailyMin: 356,daiyMax: 954),
(month:Util.getMonth(offset:10),dailyAverage:801,dailyMin: 370,daiyMax: 959),
(month:Util.getMonth(offset:11),dailyAverage:765,dailyMin: 311,daiyMax: 876),
(month:Util.getMonth(offset:12),dailyAverage:789,dailyMin: 339,daiyMax: 991),
]
Chart(data,id: \.month) {
AreaMark(
x: .value("月", $0.month, unit: .month),
yStart: .value("日最低", $0.dailyMin),
yEnd: .value("日最高", $0.daiyMax)
)
.opacity(0.3)
LineMark(
x: .value("月", $0.month, unit: .month),
y: .value("日均销量", $0.dailyAverage)
)
}
}
}
class Util {
static func getMonth(offset:Int) -> Date {
let calendar = Calendar.current
return calendar.date(byAdding: .month, value: -offset, to: Date()) ?? Date()
}
}
#Preview {
ContentView()
}
将AreaMark和LineMark改为BarMark和RectangleMark,可以得到完全不同的图表样式:
核心代码:
Chart(data,id: \.month) {
BarMark(
x: .value("月", $0.month, unit: .month),
yStart: .value("日最低", $0.dailyMin),
yEnd: .value("日最高", $0.daiyMax),
width: .ratio(0.6)
)
.opacity(0.3)
RectangleMark(
x: .value("月", $0.month, unit: .month),
y: .value("日均销量", $0.dailyAverage),
width: .ratio(0.6),
height: 2
)
}
甚至,我们可以再添加一个RuleMark,且设置annotation属性来显示对应文字 :
RuleMark(
y: .value("平均", 825)
)
.lineStyle(StrokeStyle(lineWidth: 1))
.foregroundStyle(.red)
.annotation(position: .top, alignment: .leading) {
Text("平均:825")
.font(.headline)
.foregroundStyle(.red)
}
这一篇先到这里吧,下一篇将探索Charts的自定义属性,打造更符合我们app风格的个性化图表。