Highlight
Swift Charts 新增了 SectorMark 饼图类型、chartXSelection 选择 API 和 chartScrollableAxes 滚动能力,让数据可视化从静态展示进化为可交互探索。
核心内容
Richard 用一家煎饼餐车的销售数据贯穿整场演讲。这个场景选得巧妙——六种口味的销售占比天然适合饼图,365 天的日销售数据又需要滚动浏览,城市间的对比数据则展示了选择交互的价值。
(01:13)饼图的核心是新引入的 SectorMark。它位于极坐标系中,扇形角度与数据值成正比。从柱状图改饼图只需要两行改动:把 BarMark 换成 SectorMark,把 x 参数换成 angle。
(02:35)angularInset 控制扇形间距,cornerRadius 给扇形加圆角。把 innerRadius 设为半径比例(如 0.618)就得到甜甜圈图。甜甜圈中心的空洞可以用 chartBackground 放汇总信息。
(04:24)选择交互是今年的重点。chartXSelection(value:) 修饰符处理所有手势识别,把选中的值绑定到一个状态变量。不需要自己写手势检测代码。选中后可以用 RuleMark 画一条垂直指示线,再用 annotation 显示详细信息弹窗。
(07:07)除了单值选择,还有范围选择——chartXSelection(range:) 支持选取一段区间。默认手势按平台不同:iOS 使用双指轻点,macOS 使用拖拽。也可以完全自定义手势,通过 ChartProxy 把触摸位置转换成数据值。
(07:31)饼图的选择用 chartAngleSelection(value:),选中扇形会自动高亮,其他扇形变暗。通过 opacity 修饰符可以控制未选中扇形的透明度。
(07:54)可滚动图表解决了”数据点多到塞不下屏幕”的问题。chartScrollableAxes(.horizontal) 启用横向滚动,chartXVisibleDomain(length:) 设置可见窗口大小(以秒为单位的时间间隔)。chartScrollPosition(x:) 绑定当前滚动位置,可以显示当前窗口的汇总数据。
(08:50)滚动对齐用 chartScrollTargetBehavior,支持按值对齐。演示代码让滚动停止时对齐到每天的第一个小时;用户大幅滑动时,majorAlignment 进一步要求落点匹配每月第一天。这样按月浏览销售数据时,每次都会停在月初。
详细内容
从柱状图到饼图
// 原来的柱状图
Chart(data, id: \.name) { element in
BarMark(
x: .value("Sales", element.sales),
stacking: .normalized
)
.foregroundStyle(by: .value("Name", element.name))
}
// 改成饼图——只换 Mark 类型和参数名
Chart(data, id: \.name) { element in
SectorMark(
angle: .value("Sales", element.sales)
)
.foregroundStyle(by: .value("Name", element.name))
}
关键点:
- 角度值自动归一化为完整圆,不需要手动计算百分比
foregroundStyle(by:)自动为不同类别分配颜色
精致的甜甜圈图
Chart(data, id: \.name) { element in
SectorMark(
angle: .value("Sales", element.sales),
innerRadius: .ratio(0.618),
angularInset: 1.5
)
.cornerRadius(5)
.foregroundStyle(by: .value("Name", element.name))
}
.chartBackground { chartProxy in
GeometryReader { geometry in
let frame = geometry[chartProxy.plotAreaFrame]
VStack {
Text("Most Sold Style")
.font(.callout)
.foregroundStyle(.secondary)
Text(mostSold)
.font(.title2.bold())
}
.position(x: frame.midX, y: frame.midY)
}
}
关键点:
innerRadius: .ratio(0.618)用黄金比例控制甜甜圈厚度angularInset: 1.5每个扇形内缩 1.5pt,实际间隙为 3ptchartBackground在图表绘制区域中心放置内容,用GeometryReader计算居中位置
带选择指示的折线图
struct LocationDetailsChart: View {
@Binding var rawSelectedDate: Date?
var selectedDate: Date? {
guard let rawSelectedDate else { return nil }
return data.first?.sales.first(where: {
let endOfDay = endOfDay(for: $0.day)
return ($0.day ... endOfDay).contains(rawSelectedDate)
})?.day
}
var body: some View {
Chart {
ForEach(data) { series in
ForEach(series.sales, id: \.day) { element in
LineMark(
x: .value("Day", element.day, unit: .day),
y: .value("Sales", element.sales)
)
}
.foregroundStyle(by: .value("City", series.city))
.symbol(by: .value("City", series.city))
.interpolationMethod(.catmullRom)
}
if let selectedDate {
RuleMark(
x: .value("Selected", selectedDate, unit: .day)
)
.foregroundStyle(Color.gray.opacity(0.3))
.offset(yStart: -10)
.zIndex(-1)
.annotation(
position: .top, spacing: 0,
overflowResolution: .init(
x: .fit(to: .chart),
y: .disabled
)
) {
ValuePopover(date: selectedDate)
}
}
}
.chartXSelection(value: $rawSelectedDate)
}
}
关键点:
chartXSelection返回的是原始坐标值,需要用计算属性匹配到最近的数据点RuleMark的zIndex(-1)确保指示线画在折线下方overflowResolution控制标注超出图表边界时的行为:X 轴限制在图表内,Y 轴允许超出- macOS 上选择默认用悬停手势,iOS 上用点击
可滚动的日销售图表
Chart {
ForEach(SalesData.last365Days, id: \.day) {
BarMark(
x: .value("Day", $0.day, unit: .day),
y: .value("Sales", $0.sales)
)
}
.foregroundStyle(.blue)
}
.chartScrollableAxes(.horizontal)
.chartXVisibleDomain(length: 3600 * 24 * 30)
.chartScrollPosition(x: $scrollPosition)
.chartScrollTargetBehavior(
.valueAligned(
matching: DateComponents(hour: 0),
majorAlignment: .matching(DateComponents(day: 1))
)
)
关键点:
chartXVisibleDomain(length:)参数是秒数,30 天 = 3600 * 24 * 30chartScrollPosition绑定当前位置,可用于显示当前窗口的汇总统计valueAligned让滚动停止时对齐到指定时间单位,majorAlignment控制翻页时的对齐粒度- 滚动基于 SwiftUI 的 ScrollView 增强,轴标签随内容同步滚动
饼图的选择交互
Chart(data, id: \.name) { element in
SectorMark(
angle: .value("Sales", element.sales),
innerRadius: .ratio(0.618),
angularInset: 1.5
)
.cornerRadius(5)
.foregroundStyle(by: .value("Name", element.name))
.opacity(element.name == selectedName ? 1.0 : 0.3)
}
.chartAngleSelection(value: $selectedAngle)
关键点:
chartAngleSelection专门用于极坐标图表- 通过
opacity修饰符实现选中高亮、未选中淡化 selectedAngle需要与数据值对应,通常用计算属性转换
核心启发
-
给健身 app 加一张周活动圆环的饼图变体:Apple Watch 的活动圆环本质上就是甜甜圈图。用 SectorMark 实现类似效果,中心显示总卡路里或总时长,点击扇形查看具体运动类型的明细。
-
做一款可探索的财务支出应用:把月度支出按类别做成饼图,点击某个类别后下方展开该类别的时间趋势折线图。用 chartXSelection 在折线图上选择具体日期,显示当天的每笔交易明细。
-
用可滚动图表展示长周期数据:股票、天气、健康类应用常有数百天的数据。用 chartScrollableAxes 配合 chartXVisibleDomain 只展示最近 30 天,用户左右滑动查看历史。scroll target behavior 让滑动总是对齐到月初或周一。
关联 Session
- Beyond scroll views — SwiftUI 滚动视图的底层改进,可滚动图表基于此构建
- What’s new in SwiftUI — SwiftUI 整体新特性,包括动画和数据流改进
- Animate with springs — 弹簧动画系统,图表交互中的动画过渡可用
评论
GitHub Issues · utterances