feat(chart): add multi-axis overlay controls

This commit is contained in:
李启源
2026-07-20 15:35:19 +08:00
parent 0cab86f7b6
commit c9e4dae25f
44 changed files with 1882 additions and 523 deletions

View File

@@ -3,6 +3,7 @@
## 目标
`WaveformChart.vue`1743 行)拆分为更小的、职责单一的子组件:
1. **WaveformTooltip.vue** - 悬浮提示组件
2. **WaveformTrack.vue** - 单个波形轨道组件
3. **WaveformAnnotationLayer.vue** - 标注层组件
@@ -10,6 +11,7 @@
## 当前代码分析
### 主组件职责(过多)
- ✅ 数据管理和状态协调
- ✅ 缩放和交互事件处理
- 🔴 渲染波形轨道(网格、轴、波形线、十字线)
@@ -19,13 +21,14 @@
- ✅ 编辑器管理(已拆分)
### 模板结构1055-1743 行)
```vue
<div class="waveform-chart">
<svg>
<g transform="translate(margin)">
<!-- 1. 轨道循环100+ 包含网格标签波形线十字线 -->
<g v-for="track in trackLayouts">...</g>
<!-- 2. 标注和图形层135 -->
<g class="waveform-chart__markup-layer">
<g v-for="shape in renderedShapes">...</g>
@@ -34,10 +37,10 @@
</g>
</g>
</svg>
<!-- 3. Tooltip15 -->
<div v-if="showTooltip && hoveredPoint" class="waveform-chart__tooltip">...</div>
<!-- 4. 已拆分组件 -->
<WaveformToolbar />
<WaveformEditor />
@@ -51,10 +54,11 @@
**职责**:显示鼠标悬浮时的数据点信息
**Props**
```typescript
interface Props {
visible: boolean
position: { x: number, y: number }
position: { x: number; y: number }
timeUnit: 's' | 'ms'
hoveredPoint: WaveformPoint | null
seriesPoints: Array<{
@@ -68,6 +72,7 @@ interface Props {
```
**提取内容**
- 模板1463-1478 行16 行)
- 样式1682-1742 行61 行)
- 计算属性:`tooltipStyle`471-477 行)
@@ -79,11 +84,12 @@ interface Props {
**职责**渲染单个波形轨道网格、坐标轴、波形线、十字线、overlay
**Props**
```typescript
interface Props {
track: TrackLayout
clipPathId: string
margin: { top: number, right: number, bottom: number, left: number }
margin: { top: number; right: number; bottom: number; left: number }
innerWidth: number
showTooltip: boolean
zoomable: boolean
@@ -91,11 +97,12 @@ interface Props {
activeInteractionMode: WaveformInteractionMode
frameNumber?: string | number
timeUnit: 's' | 'ms'
hoveredPoint?: HoveredSeriesPoint // 用于显示十字线
hoveredPoint?: HoveredSeriesPoint // 用于显示十字线
}
```
**Emits**
```typescript
interface Emits {
(e: 'pointer-move', event: PointerEvent): void
@@ -108,6 +115,7 @@ interface Emits {
```
**提取内容**
- 模板1099-1265 行166 行)
- 相关函数:
- `shouldShowYAxisLabel` (247-262)
@@ -119,6 +127,7 @@ interface Emits {
- 样式:部分轨道相关样式
**注意事项**
- 轨道组件需要在父组件中接收 D3 渲染的坐标轴
- 或者在 `onMounted` 中自己调用 D3 渲染坐标轴
@@ -129,6 +138,7 @@ interface Emits {
**职责**渲染所有标注和图形annotations + shapes
**Props**
```typescript
interface Props {
renderedAnnotations: RenderedAnnotation[]
@@ -142,6 +152,7 @@ interface Props {
```
**Emits**
```typescript
interface Emits {
(e: 'select-markup', kind: 'annotation' | 'shape', id: string): void
@@ -150,6 +161,7 @@ interface Emits {
```
**提取内容**
- 模板1285-1419 行135 行)
- 相关函数:
- `isSelected` (491-493)
@@ -166,12 +178,14 @@ interface Emits {
## 实施步骤
### 阶段 1: 创建 WaveformTooltip.vue ✅
1. 创建组件文件
2. 提取模板和样式
3. 实现计算属性 `tooltipStyle`
4. 更新主组件使用新组件
### 阶段 2: 创建 WaveformAnnotationLayer.vue ✅
1. 创建组件文件
2. 提取标注层模板
3. 提取相关工具函数
@@ -179,6 +193,7 @@ interface Emits {
5. 更新主组件
### 阶段 3: 创建 WaveformTrack.vue ✅
1. 创建组件文件
2. 提取轨道渲染逻辑
3. 处理 D3 坐标轴渲染(使用 `ref` + `onMounted`
@@ -186,6 +201,7 @@ interface Emits {
5. 更新主组件
### 阶段 4: 验证和测试 ✅
1. 运行类型检查 `pnpm typecheck`
2. 运行代码规范检查 `pnpm lint`
3. 运行单元测试 `pnpm test`
@@ -196,20 +212,25 @@ interface Emits {
## 设计决策
### 1. 类型共享
`DisplaySeries`, `HoveredSeriesPoint`, `TrackLayout`, `RenderedAnnotation`, `RenderedShape` 等接口移到独立的类型文件中,供多个组件使用。
**创建** `src/components/waveform-chart-types.ts`
### 2. D3 渲染策略
对于 WaveformTrack 中的坐标轴渲染:
- **方案 A推荐**:在 Track 组件内部使用 `ref` + `onMounted` 调用 D3
- **方案 B**:父组件渲染后通知子组件
- **选择 A**:更符合组件封装原则
### 3. 事件冒泡
所有交互事件click, pointer-move 等)通过 emit 向上传递,保持主组件的事件协调职责。
### 4. 样式隔离
每个组件使用 `<style scoped>`,但共享的样式变量可以提取到 CSS 变量中。
---
@@ -217,6 +238,7 @@ interface Emits {
## 预期收益
### 代码规模
- **主组件**1743 → ~1100 行(-37%
- **新组件**
- WaveformTooltip: ~80 行
@@ -224,11 +246,13 @@ interface Emits {
- WaveformTrack: ~300 行
### 可维护性
- 每个组件职责清晰
- 修改轨道渲染不影响标注层
- Tooltip 可独立测试和复用
### 可测试性
- 每个子组件可独立单元测试
- 减少主组件测试的复杂度
@@ -237,19 +261,23 @@ interface Emits {
## 风险和注意事项
### 1. D3 上下文问题
坐标轴渲染依赖 D3 操作 DOM需要确保 ref 正确传递和挂载时机。
**解决方案**:在 Track 组件中使用 `watch` 监听 `track` prop 变化,触发重新渲染。
### 2. 性能影响
拆分组件可能增加 Vue 的更新开销。
**解决方案**
- 使用 `shallowRef` 存储 D3 对象
- 对于大数组tracks, annotations使用稳定的 `:key`
- 如有性能问题,可使用 `v-memo` 指令
### 3. 向后兼容
确保 props 和 emits 接口不变。
**验证方法**:运行现有的 24 个单元测试。