大多数浏览器和
Developer App 均支持流媒体播放。
-
Swift Charts:矢量图与函数图
数据图更丰富了!了解如何利用函数图和矢量图,在你的 App 中渲染精美的图表来表示数学函数和大规模数据集。无论你是希望显示空气动力学、磁学和高阶场论的常见函数,还是创建大型交互式热图,总能在 Swift Charts 中找到合适的图表。
章节
- 0:00 - Introduction
- 1:01 - Function plots
- 6:48 - Vectorized plots
- 11:27 - Best practices
资源
相关视频
WWDC23
WWDC22
-
下载
大家好 我叫 Apollo 我将为大家介绍 Swift Charts 中的新功能 Swift Charts 支持使用 SwiftUI 创建信息丰富、 易于访问 且赏心悦目的可视化图表
本次更新带来了新的图表类型 可以呈现天气趋势、
追踪你的情绪和生命体征
并能在 Math Notes 中绘制图形函数
没错!
现在借助 Swift Charts 你可以在 App 中绘制数学函数 从而直观呈现数据 之外的内容
Swift Charts 现在还集成了 矢量化绘图 API 支持你更高效地直观呈现 更大的数据集 我准备了这个视频 为大家详细介绍 Swift Charts 这些激动人心的新功能 我们首先来看看函数图 它引入了两个新的 API:
LinePlot 用于视觉化呈现单个函数 AreaPlot 用于填充两个函数之间的区域 让我带你了解 函数图如何帮助我 进行数据分析
我一直在研究 美国本土的 大型太阳能项目 使用的数据集来自美国地质调查局
我构建了一个直方图 通过 ForEach 循环遍历所有数据点 并为每个元素使用了 BarMark 来直观呈现太阳能电池板的容量密度 结果 这个直方图表明容量密度 可能呈正态分布 我将用新的函数图 API 绘制正态分布曲线 来进行对比分析
我定义了一个函数 用于计算正态分布曲线上的点
我可以使用新的 LinePlot API 来绘制这条曲线 这个 API 接受一个接收 并返回双精度浮点数的闭包 因此我可以使用事先计算的 均值和标准差来调用我的函数
Swift Charts 的基本原则是 要让任何人都可以使用数据可视化 Swift Charts 默认赋予了图表无障碍访问性
我可以使用旁白 来描述图表 “x 轴表示容量密度 Y 轴表示概率 图表中包含两个数据系列” 音频图也适用于函数图
“完成”
很好!就像在 SwiftUI 中一样 你可以使用修饰符 来自定义函数的外观
在这里 由于 LinePlot 的默认颜色 与柱状图相同 因此最好使用不同的 foregroundStyle 来 自定函数图的颜色
现在看上去好多了 但是为了使它更加显眼 我想要填充 曲线下方的区域 为此 我只需将 LinePlot 改为 AreaPlot 即可
为了提高对比度 我可以 调整透明度进一步自定 AreaPlot 让它更清晰易读
这就是绘制 简单数学函数的方法 在 Swift Charts 中 构建更高级的函数图也一样简单
例如 AreaPlot 不仅可以 直观呈现曲线下方的区域 你还可以用它来呈现 两个函数之间的区域 具体方法是 为给定输入 x 返回一对 yStart 和 yEnd 值
与数据可视化不同 函数可以接受无限范围的 x 值 默认情况下 Swift Charts 会通过对函数采样 来自动推断域
但是我可以通过 设置 X 轴和 Y 轴的比例尺 来自定图表的整体范围 从而只包括我感兴趣的函数部分
我还能够限制函数图本身的定义域
通过限制 AreaPlot 的采样域 图表现在仅展示 这个函数的中间部分
Swift Charts 还支持 绘制参数函数
这是一个参数函数 其中 x 和 y 是根据第三个变量 t 定义的 让我们把它绘制出来
你可以通过同一个 LinePlot API 在 Swift Charts 中 绘制参数函数 但需要根据 参数 t 同时返回 x 和 y 值 我喜欢这个图形!
接下来我们谈谈 如何处理分段函数
有时分段函数 在它的定义域内的某些值上 没有对应的输出
在这些情况下 你可以 返回 .nan 以通知 Swift Charts 这个输入值 x 没有对应的数值
而在其他情况下 你的代码可能会在某些 x 值上出错 例如在计算 1/x 时 x 等于 0 的情况
你同样应该通过返回 .nan 来处理特殊值
以上就是 使用折线图和面积图绘制函数 它们将整个 数学函数视为单个实体 但绘图 API 不仅可用于函数
还可以更方便、更高效地 直观呈现更大的数据集
因此我们也为所有其他标记类型 添加了绘图 API 变体
这些矢量图 API 可以并行处理整个集合 绘制出丰富的数据可视化效果 例如分类模型的散点图 或是 Transformer 语言模型中的 自注意力热图 但在深入探讨之前 我们先来回顾一下 如何使用 Mark API 声明图表
Mark API 非常灵活 允许你为每个数据点 单独设置不同的样式 从选择应用哪些修饰符 到选择使用哪种标记
但在大多数情况下 你不需要这种程度的定制 通常 整个数据点集合的样式都是相同的 对 X、Y、foregroundStyle 和其他视觉属性 使用相同的元素属性
相比之下 新的矢量图 API 比如 RectanglePlot 允许 Swift Charts 更高效地 处理更大的数据集合 有关矢量图的示例 让我们 回到前面的太阳能电池板数据集
我想将展示所有太阳能板安装情况的 可视化效果添加到 App 中
对于这个图表中的所有数据点 我想以相同的方式自定它们 大小将由容量决定
颜色则根据面板的轴类型而变化
数据集中包含以经度和纬度表示的 原始 GPS 坐标 但我希望用阿尔伯斯投影 在平面图上展示这些点
我可以在扩展中添加计算属性 来即时完成坐标转换 但为了充分利用 矢量图 我选择将它们添加为 存储属性
存储属性使得 Swift Charts 能够使用固定的内存偏移 来读取所有数据点的 x 和 y 值 而不是 对每个数据点逐一调用取值方法
新的 PointPlot API 能够接收整个数据集合来进行绘制
对于绘图中所有点的 x 和 y 值 我可以采用相同的 .value 语法 并配以标签 以及指向 DataPoint 结构体中 x 和 y 这两个存储属性的 KeyPath
如果你以前使用过 SwiftUI 可能已经用过 KeyPaths 使用 KeyPaths Swift Charts 可设置所有点的样式 而无需遍历数据集
矢量图的修饰符 同样接受 keyPaths
通过使用 symbolSize 我用点的大小代表太阳能电池板容量 同样 我使用太阳能电池板 轴类型的键路径 来自定每个点的颜色
所用其他常用于 统一修改的修饰符 也都支持键路径参数
凭借空间计算技术 我的 App 在 Apple Vision Pro 中效果很出色 左侧的矢量图 动画流畅 当我浏览柱状图时 仪表盘上的所有图表 都能同步更新
通过双指开合和拖动 我能仔细查看刚添加的矢量图 了解有关 每处太阳能电池板安装的更多信息 并迅速浏览 我们之前创建的正态分布图
以上就是关于矢量图的介绍 现在你可能想知道 矢量图与标记是如何协同工作的
对于使用相同修饰符和属性 自定整个绘图的较大数据集 应使用矢量图 API
如果你的数据点较少 但需要使用单独的标记类型 和修饰符自定每个元素 或需使用 zIndex
实现复杂层次叠加时 应使用 Mark API
使用矢量图时 可以通过按将用于数据的样式 对数据集合进行分组 帮助 Swift Charts 减少 样式变换的次数
在渲染过程中避免 其他计算也有帮助 例如将计算属性 转换为存储属性
如果你已经知道 将要使用的几种不同样式 或图表的整体边界 那么对它们进行指定将能更高效地 呈现你的图表
最后 如果数据点的数量众多 某些样式定制可能会变得并不明显 因此你完全可以跳过这些设置 让你的图表性能更强
以上就是对 Swift Charts 中 全新的矢量图和函数图的介绍
不妨试用一下这些新功能 让你的可视化效果更上一层楼 此外 下载示例项目 查看更多函数图示例 包括如何为它们添加交互功能 如果你刚开始接触 Swift Charts 请观看之前的讲座 快速入门
感谢观看 期待看到大家用 Swift Charts 绘制的精彩内容
-
-
1:43 - Histogram that shows distribution of capacity density
Chart { ForEach(bins) { bin in BarMark( x: .value("Capacity density", bin.range), y: .value("Probability", bin.probability) ) } }
-
2:18 - Visualize function with LinePlot
Chart { LinePlot( x: "Capacity density", y: "Probability" ) { x in // (Double) -> Double normalDistribution( x, mean: mean, standardDeviation: standardDeviation ) } }
-
3:36 - Customize function plot with modifiers
Chart { LinePlot( x: "Capacity density", y: "Probability" ) { x in normalDistribution(x, ...) } .foregroundStyle(.gray }
-
3:57 - Visualize area under a curve with AreaPlot
Chart { AreaPlot( x: "Capacity density", y: "Probability" ) { x in normalDistribution(x, ...) } .foregroundStyle(.gray) .opacity(0.2) }
-
4:21 - Visualize area between curves with AreaPlot
Chart { AreaPlot( x: "x", yStart: "cos(x)", yEnd: "sin(x)" ) { x in (yStart: cos(x / 180 * .pi), yEnd: sin(x / 180 * .pi)) } }
-
4:59 - Specify domain for function plots
Chart { AreaPlot( x: "x", yStart: "cos(x)", yEnd: "sin(x)" ) { x in (yStart: cos(x / 180 * .pi), yEnd: sin(x / 180 * .pi)) } } .chartXScale(domain: -315...225) .chartYScale(domain: -5...5)
-
5:18 - Specify sampling domain for function plots
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)) } } .chartXScale(domain: -315...225) .chartYScale(domain: -5...5)
-
5:55 - Visualize parametric functions
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)
-
6:40 - Use Double.nan to represent no value
Chart { LinePlot(x: "x", y: "1 / x") { x in guard x != 0 else { return .nan } return 1 / x } } .chartXScale(domain: -10...10) .chartYScale(domain: -10...10)
-
7:43 - Highly customized Chart
Chart { ForEach(model.data) { if $0.capacityDensity > 0.0001 { RectangleMark( x: .value("Longitude", $0.x), y: .value("Latitude", $0.y) ) .foregroundStyle(by: .value("Axis type", $0.axisType)) } else { PointMark( x: .value("Longitude", $0.x), y: .value("Latitude", $0.y) ) .opacity(0.5) } } }
-
8:00 - Homogeneously styled Chart
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) } }
-
8:23 - Vectorized plot for homogeneously styled chart
Chart { RectanglePlot( model.data, x: .value("Longitude", \.x), y: .value("Latitude", \.y) ) .foregroundStyle(by: .value("Axis type", \.panelAxisType)) .opacity(\.capacityDensity) }
-
9:42 - Vectorized point plot API
Chart { PointPlot( model.data, x: .value("Longitude", \.x), y: .value("Latitude", \.y) ) }
-
10:26 - Vectorized plot modifiers
Chart { PointPlot( model.data, x: .value("Longitude", \.x), y: .value("Latitude", \.y) ) .symbolSize(by: .value("Capacity", \.capacity)) .foregroundStyle( by: .value("Axis type", \.panelAxisType) ) }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。