Chart:LinePlot 与 AreaPlot(函数曲线)
LinePlot / AreaPlot(iOS 18+)用一个 JS 函数 直接画曲线 / 面积,不需要预先准备数据数组。SwiftUI Charts 在每次 chart layout 时按 viewport 宽度自动采样 fn 并连成连续曲线。
bridge 通过 emitWidgetEventSync(跟同步 gesture / drop callback 同一条通道)把 JS 回调串到 swift 端,每一次采样就是一次 JSCore round-trip。
iOS 17 fallback:bridge 打印
API deprecated警告并不挂任何 mark,同 chart 内其他 mark 不受影响。
API —— 三种形态
1. 单变量 y = fn(x)
domain可省略,省略时 SwiftUI Charts 用 chart 的 visible x domain 决定采样区间。fn(x: number) => number。返回NaN或非有限值 → SwiftUI Charts 跳过该 sample。
2. 参数化 (x, y) = fn(t)
- bridge 按
t字段是否存在分发到参数化形态。 domain必填(参数化没有"可见 domain"概念,没默认)。fn必须返回{ x, y },缺字段 → 该 sample 当 NaN 跳过。
3. AreaPlot (yStart, yEnd) = fn(x)
- 在每个 x 上填充
yStart到yEnd的垂直区间。常用于 confidence band / envelope。
三种形态都接受标准 ChartMarkProps(foregroundStyle / opacity / lineStyle / interpolationMethod / accessibilityLabel / ...),跟其他 mark 一样走 applyModifiers 路径。
性能与正确性
- 闭包必须保持纯。 SwiftUI Charts 在每次 chart layout 都会重新执行 fn。在 fn 内
setState或别的 React 状态变更会触发死循环(与<ChartGesture>/<ChartOverlay>/<ChartPlotStyle>闭包同样的坑)。 - 每个 sample 是一次 JSCore 调用(~5µs)。 一个 400 像素宽的 chart 每次 layout 重采样 ≈ 400 次,约 2 ms。静态 / 偶尔更新的 chart 没问题;连续滚动 / pinch / 每帧 state 更新场景下可能感知到 jank。
- 稳定 React re-render。 chart 周围每次 React render 都会重建
fn引用、注册一个新 callback id、SwiftUI 看到一个新的LinePlot值,触发完整 re-layout。useCallback能稳定 JS 端引用,但 阻止不了 SwiftUI 端重 layout —— bridge 每次都新建一个 LinePlot struct。要减少 re-render 请把 state 提到 chart 外,或 memo 外层组件。 - Callback-id 累积。 当前每次 render 都注册新 callback id,旧 id 留在 component 的 callback map 里直到 unmount。长寿命 chart + 高频更新场景下 map 会按 O(渲染次数 × plot 数) 增长。一般使用没问题;高频 scrubbing UI 注意。
- 抛错 / 非有限。 fn 抛错、返回
undefined、返回NaN/Infinity→ bridge 替换为Double.nan,SwiftUI Charts 跳过该 sample。不要拿这个做流控,请设计 fn 在整个 domain 上是 total 的。 @Sendable线程。 SwiftUI Charts 把 closure 标了@Sendable;当前 SDK 行为是 main thread 调用(JSContext 所在线程),bridge 加了assert兜底。未来 SDK 如果放到 off-main,assert 会捕获。
相关
ChartGesture—— 由用户手势驱动的闭包,同样要求纯 body。ChartPlotStyle—— 另一种 reader-style mark 子组件,闭包也是反复 evaluate。
