图表浮层与区间选择
本示例同时演示四个图表交互/自定义能力:
ChartOverlay——<Chart>的 reader-style 子组件,通过ChartProxy反查"屏幕坐标↔数据值",做自定义 hit-test、tooltip、覆盖图层。对应 SwiftUI Charts 的chartOverlay(alignment:content:) { proxy in ... }。- 区间选择 —— 给
chartXSelection/chartYSelection传from / to(而不是value),bridge 自动选择 SwiftUI 的chartXSelection(range:)/chartYSelection(range:)重载。 ChartGesture——<Chart>的另一种 reader-style 子组件,闭包返回任意Gesture描述子,并把一个可写入选区的ChartProxy注入闭包;用于绕过默认手势限制(例如在字符串轴上做单指拖动 range)。对应chartGesture(_:) { proxy in ... }。ChartPlotStyle——<Chart>的另一种 reader-style 子组件,闭包接收一个 builder proxy,链式追加 plot 区域 modifier(background / border / frame / shadow / cornerRadius / clipShape / opacity)。对应chartPlotStyle { plot in plot.background(...).border(...) }。
ChartOverlay 用法
ChartProxy 全部方法同步;当 type token 与图表实际坐标轴数据类型不匹配时返回 null:
区间选择用法
- bridge 按
from / to是否存在自动分流到 SwiftUI 的chartXSelection(range:)重载。原有单值形态(value+onChanged)100% 保留。 valueType: 'string' | 'number' | 'date'—— 必须与图表坐标轴实际数据类型一致。onChanged在选区改变时触发,清空选区时回调null。
轴类型限制:区间选择只在连续轴(number / date)上生效。SDK 在 categorical String 轴上既不响应默认 range 手势,也无法把像素坐标反查回字符串类目,所以即使配合下面的
<ChartGesture>+proxy.selectXRange也无效。如果要在 String 轴上做选择,请用单值形态ChartSelection。
激活手势(按平台不同)
chartXSelection(range:) 默认手势随平台变化,这是 SwiftUI Charts SDK 行为,不是 bridge 限制:
- iOS:图表上的双指 tap。iOS 模拟器请按住 ⌥ Option + 点击 来模拟双指。
- macOS:拖动手势。
单指长按 + 拖动不会默认触发 range 选择。如要单指手势 / 自定义触发条件,用下面的 <ChartGesture> 接管手势。
来源:Mastering charts in SwiftUI · Selection、WWDC23 · Explore pie charts and interactivity in Swift Charts。
同一条轴上单值与区间形态互斥。点选用单值,拖选区间用 range 形式。
Axis label 精度(ChartAxisLabelFormat)
chartXAxis / chartYAxis 的 valueLabel.format 字段除了接受短字符串 token('number' | 'percent' | 'currency' | 'date' | 'time' | 'dateTime'),还接受 native 类 ChartAxisLabelFormat 的实例,支持小数位、货币代码、日期/时间风格等参数(对应 SwiftUI Foundation 的 FormatStyle)。
可用工厂:
短字符串 token 100% 保留,二者随便选。需要精度/货币/style 时用工厂;其他场景仍可写
format: 'number'这种简洁形式。
ChartGesture 用法
- 闭包返回一个
Gesture描述子(DragGesture()/TapGesture()/LongPressGesture()/MagnifyGesture()/RotateGesture()),等价 SwiftUI 的chartGesture { proxy in ... }。 - 闭包内的
proxy.selectXRange / selectYRange / selectXValue / selectYValue / selectAngleValue接收的是屏幕像素坐标(不是数据值)—— 直接传DragGesture事件的startLocation.x/location.x即可,无需反算数据值。 - 写入选区后,对应的
chartXSelection / chartYSelection / chartAngleSelectionbinding 会回调,把数据值通过onChanged回到 JS 端。 - 一个 chart 只取第一个
<ChartGesture>子组件(与<ChartOverlay>同规则)。 - 适合取代 SDK 默认手势:单指拖动、自定义激活条件等都可以替代默认双指 tap。
- 轴类型限制:和默认手势一样,仅 number / date 轴有效;categorical String 轴上 SDK 无法把像素反查回字符串类目,因此
proxy.selectXRange在字符串轴上也不会回调。
ChartPlotStyle 用法
闭包接收一个空的 ChartPlotProxy,必须返回一个(一般是链式调用后的)ChartPlotProxy。每次链式调用返回一个新的 immutable proxy 并累计一个 op;bridge 会在 SwiftUI Charts 的 chartPlotStyle { plot in ... } 闭包内把 ops 重放到真实 ChartPlotContent 视图上。
可用 builder 方法:
Material token 取值:'ultraThin' / 'thin' / 'regular' / 'thick' / 'ultraThick' / 'bar'(可加 Material 后缀,例如 'regularMaterial')。
与
<ChartOverlay>/<ChartGesture>一样,一个 chart 只取第一个<ChartPlotStyle>子组件。闭包 body 必须保持纯 —— 在内部setState会触发 chart 重 build → 闭包又跑 → 死循环。
Mark Accessibility(VoiceOver 三件套)
每个 mark 在自己的 ChartMarkProps 上接收三个可选的无障碍字段:
三个字段在所有 mark 类型(BarMark / LineMark / PointMark / RuleMark / RectangleMark / AreaMark / 扇区等)上都通用,走的是 ChartContent.applyModifiers 同一路径(与 foregroundStyle / opacity 等一致)。
验证方式:真机或模拟器开
设置 → 辅助功能 → 旁白 (VoiceOver),在 chart 上轻扫,然后右滑切换 mark,应该听到你设置的字符串。
注意事项
ChartOverlay首次同步渲染时 proxy 为null。<ChartOverlay>在 SwiftUI 完成 chart 构建并注入真实 proxy 之前会回落到EmptyView。请在闭包内对该情况做兜底。SelectedRange / selectedRangeAxis不在ChartProxy上暴露。 SwiftUI Charts 没有通过ChartProxy暴露区间选区状态 —— 请通过chartXSelection(range:)/chartYSelection(range:)的 binding 自行观察。TS 接口刻意未提供这两个方法。chartOverlay没有spacing参数,仅支持alignment(与 SwiftUI 原生 API 一致)。- overlay 内容尽量轻。SwiftUI 在每次 chart 重建时都会重新调用 overlay 闭包,请避免在内部做重计算或启动异步任务。
<ChartGesture>/<ChartOverlay>闭包 body 必须保持纯——SwiftUI Charts 在每次 chart 重建时都会重新执行此闭包,body 内调setState会立即触发外层 React 重渲染 → chart 又重 build → 闭包又跑 → 死循环。状态写入只能放在onChanged / onEnded这种用户手势事件 callback 里。ChartGesture闭包返回的必须是GestureInfo(DragGesture()/TapGesture()等的返回值)。返回null或其他类型会被忽略。
