WWDC Quick Look 💓 By SwiftGGTeam
Swift Charts: Vectorized and function plots

Swift Charts: Vectorized and function plots

观看原视频

Highlight

Swift Charts 新增函数绘图和向量化绘图 API,前者支持直接绘制数学函数曲线,后者可高效渲染大规模数据集。


核心内容

Swift Charts 在 iOS 16 发布时,定位是用 SwiftUI 的方式画数据图表。开发者通过 ForEach 遍历数据,用 BarMark、PointMark 等 Mark 类型构建可视化。这套 API 很灵活,可以为每个数据点单独定制样式。

但实际场景中,很多图表的视觉风格是统一的——所有点用同样的符号大小、颜色映射规则。这种情况下,ForEach 逐个处理就有些”杀鸡用牛刀”。更常见的问题在于科学计算领域:正态分布、参数方程这些数学函数,本身是连续的定义域,无法用离散的数据点精确表达。

今年的更新针对这两类场景:LinePlot 和 AreaPlot 用于绘制数学函数,向量化 Plot API(PointPlot、RectanglePlot 等)用于高效处理大规模数据集。两个方向的设计理念一致——把”整体”当作一个实体来处理,而非逐个元素的循环。


详细内容

函数绘图

LinePlot 和 AreaPlot 是两个新 API,接受一个闭包,输入 x 返回 y(或返回 x、y 的元组)。闭包内可以调用任何数学函数:

Chart {
  LinePlot(
    x: "Capacity density", y: "Probability"
  ) { x in
    normalDistribution(
      x,
      mean: mean,
      standardDeviation: standardDeviation
    )
  }
}

关键点

  • LinePlot 接受 x 和 y 标签,闭包签名为 (Double) -> Double
  • normalDistribution 是自定义函数,预计算了均值和标准差
  • Swift Charts 自动采样函数生成曲线

若要填充曲线下方的区域,改用 AreaPlot(03:57):

Chart {
  AreaPlot(
    x: "Capacity density", y: "Probability"
  ) { x in
    normalDistribution(x, ...)
  }
  .foregroundStyle(.gray)
  .opacity(0.2)
}

关键点

  • AreaPlot 填充曲线下方区域
  • foregroundStyle 设置填充颜色
  • opacity 调整透明度以增强可读性

AreaPlot 也支持绘制两条曲线之间的区域,闭包返回 yStart 和 yEnd 的元组(04:21):

Chart {
  AreaPlot(
    x: "x", yStart: "cos(x)", yEnd: "sin(x)"
  ) { x in
    (yStart: cos(x / 180 * .pi),
     yEnd: sin(x / 180 * .pi))
  }
}

关键点

  • 闭包返回 (yStart: Double, yEnd: Double) 元组
  • 角度需转换为弧度:x / 180 * .pi
  • 用于可视化两个函数间的差值或积分区域

函数的定义域默认由 Swift Charts 自动推断,也可手动指定(04:59):

Chart {
  AreaPlot(...)
}
.chartXScale(domain: -315...225)
.chartYScale(domain: -5...5)

关键点

  • .chartXScale.chartYScale 设置坐标轴范围
  • 用于放大或缩小函数的特定部分

若只需绘制函数的某一段,可在 Plot 上指定采样域(05:18):

Chart {
  AreaPlot(
    x: "x", yStart: "cos(x)", yEnd: "sin(x)",
    domain: -135...45
  ) { x in
    (yStart: cos(x / 180 * .pi),
     yEnd: sin(x / 180 * .pi))
  }
}

关键点

  • domain 参数限制函数的采样范围
  • .chartXScale 分开控制:前者约束采样,后者控制显示范围

Swift Charts 支持参数方程绘图,x 和 y 都用第三个变量 t 表示(05:55):

Chart {
  LinePlot(
    x: "x", y: "y", t: "t", domain: -.pi ... .pi
  ) { t in
    let x = sqrt(2) * pow(sin(t), 3)
    let y = cos(t) * (2 - cos(t) - pow(cos(t), 2))
    return (x, y)
  }
}
.chartXScale(domain: -3...3)
.chartYScale(domain: -4...2)

关键点

  • LinePlot 增加 t 参数和 domain
  • 闭包接受 t,返回 (x, y) 元组
  • 用于绘制心形线、螺旋线等参数曲线

分段函数可能对某些输入无定义,返回 .nan 即可(06:40):

Chart {
  LinePlot(x: "x", y: "1 / x") { x in
    guard x != 0 else {
      return .nan
    }
    return 1 / x
  }
}

关键点

  • .nan 表示该 x 值无对应的 y 值
  • 用于处理除零、对数负数等非法输入
  • Swift Charts 会跳过这些点,不绘制连线

向量化绘图

向量化 Plot API 用于处理大规模数据集。传统方式是 ForEach + Mark,每个数据点独立处理(07:43):

Chart {
  ForEach(model.data) {
    RectangleMark(
      x: .value("Longitude", $0.x),
      y: .value("Latitude", $0.y)
    )
    .foregroundStyle(by: .value("Axis type", $0.panelAxisType))
    .opacity($0.capacityDensity)
  }
}

关键点

  • ForEach 遍历每个数据点
  • 每个 Mark 独立设置样式属性
  • 适合需要差异化定制的场景

若所有点样式统一,可改用 RectanglePlot,用 KeyPath 一次性处理整个集合(08:23):

Chart {
  RectanglePlot(
    model.data,
    x: .value("Longitude", \.x),
    y: .value("Latitude", \.y)
  )
  .foregroundStyle(by: .value("Axis type", \.panelAxisType))
  .opacity(\.capacityDensity)
}

关键点

  • RectanglePlot 接受整个数据集合
  • \.x\.y 是 KeyPath,指向存储属性
  • foregroundStyleopacity 也用 KeyPath 批量设置

PointPlot 同理,用于散点图(09:42):

Chart {
  PointPlot(
    model.data,
    x: .value("Longitude", \.x),
    y: .value("Latitude", \.y)
  )
}

关键点

  • PointPlot 是散点图的向量化版本
  • x 和 y 参数接受 KeyPath 而非闭包

向量化 Plot 的修饰符同样支持 KeyPath(10:26):

Chart {
  PointPlot(
    model.data,
    x: .value("Longitude", \.x),
    y: .value("Latitude", \.y)
  )
  .symbolSize(by: .value("Capacity", \.capacity))
  .foregroundStyle(by: .value("Axis type", \.panelAxisType))
}

关键点

  • symbolSize 用 KeyPath 映射到点的尺寸
  • foregroundStyle 用 KeyPath 映射到颜色
  • 批量应用样式,无需遍历

向量化绘图要求属性是存储属性,而非计算属性。因为计算属性每次调用都需重新计算,存储属性则可用固定内存偏移量访问(09:14)。

Swift Charts 的无障碍支持默认适用于函数绘图。Audio Graph 可自动生成函数曲线的音频描述,VoiceOver 能朗读坐标轴和数据系列信息(03:00)。


核心启发

1. 函数可视化用于数据对比

在数据分析时,若怀疑样本符合某种统计分布,用 LinePlot 叠加理论曲线,直观对比实际分布与理论模型的差异。开始只需定义好分布函数,调用 LinePlot 绘制,用颜色或透明度区分。

2. 向量化 API 处理大规模数据集

散点图或热力图的数据量超过 100 个点时,优先考虑向量化 Plot API。把 ForEach 循环换成 PointPlot 或 RectanglePlot,用 KeyPath 指定属性,渲染效率会明显提升。注意把计算属性改成存储属性。

3. 分段函数用 .nan 处理无定义点

绘制有定义域限制的函数时,在无定义区间返回 .nan。除零、对数负数、分段函数的缺口都能用此方式处理,Swift Charts 会自动断开连线。


关联 Session

评论

GitHub Issues · utterances