|
| 1 | +# Design Document: Animation-Driven Visualization |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +本设计将【算法执行状态】面板(AlgorithmGuide)删除,将其功能通过动画和浮动标注的形式整合到递归树画布中。核心设计理念是**动画即叙事**——通过精心编排的视觉动效来传达算法执行过程,而非依赖文字说明。 |
| 6 | + |
| 7 | +### 设计目标 |
| 8 | + |
| 9 | +1. **沉浸式体验**:用户通过观看动画理解算法,无需阅读大段文字 |
| 10 | +2. **视觉层次清晰**:通过颜色、动画、大小区分不同状态的节点和边 |
| 11 | +3. **上下文感知**:在关键时刻显示简短的浮动提示,自动消失 |
| 12 | +4. **流畅过渡**:所有状态变化都有平滑的动画过渡 |
| 13 | + |
| 14 | +## Architecture |
| 15 | + |
| 16 | +``` |
| 17 | +┌─────────────────────────────────────────────────────────────┐ |
| 18 | +│ App.tsx │ |
| 19 | +│ ┌─────────────┐ ┌──────────────────────────────────────┐ │ |
| 20 | +│ │ LeftPanel │ │ CenterPanel │ │ |
| 21 | +│ │ │ │ ┌────────────────────────────────┐ │ │ |
| 22 | +│ │ - Input │ │ │ TreeVisualization │ │ │ |
| 23 | +│ │ - Control │ │ │ ┌──────────────────────────┐ │ │ │ |
| 24 | +│ │ - Keypad │ │ │ │ SVG Canvas │ │ │ │ |
| 25 | +│ │ - Path │ │ │ │ - Animated Nodes │ │ │ │ |
| 26 | +│ │ - Results │ │ │ │ - Animated Edges │ │ │ │ |
| 27 | +│ │ │ │ │ │ - Floating Annotations │ │ │ │ |
| 28 | +│ │ │ │ │ │ - Path Highlight │ │ │ │ |
| 29 | +│ │ │ │ │ └──────────────────────────┘ │ │ │ |
| 30 | +│ │ │ │ │ ┌──────────────────────────┐ │ │ │ |
| 31 | +│ │ │ │ │ │ Compact Legend │ │ │ │ |
| 32 | +│ │ │ │ │ └──────────────────────────┘ │ │ │ |
| 33 | +│ │ │ │ └────────────────────────────────┘ │ │ |
| 34 | +│ └─────────────┘ └──────────────────────────────────────┘ │ |
| 35 | +└─────────────────────────────────────────────────────────────┘ |
| 36 | +``` |
| 37 | + |
| 38 | +## Components and Interfaces |
| 39 | + |
| 40 | +### 1. TreeVisualization (Enhanced) |
| 41 | + |
| 42 | +增强后的树可视化组件,整合所有动画和标注功能。 |
| 43 | + |
| 44 | +```typescript |
| 45 | +interface TreeVisualizationProps { |
| 46 | + treeData: TreeNode | null; |
| 47 | + currentNodeId: string | null; |
| 48 | + highlightedPath: string | null; |
| 49 | + currentStep: AnimationStep | null; // 新增:当前步骤信息 |
| 50 | + previousStep: AnimationStep | null; // 新增:上一步骤(用于检测变化) |
| 51 | + playbackSpeed: number; // 新增:播放速度(影响动画时长) |
| 52 | +} |
| 53 | +``` |
| 54 | + |
| 55 | +### 2. AnimationConfig |
| 56 | + |
| 57 | +动画配置接口,用于统一管理动画参数。 |
| 58 | + |
| 59 | +```typescript |
| 60 | +interface AnimationConfig { |
| 61 | + baseDuration: number; // 基础动画时长 (ms) |
| 62 | + nodeEnterDuration: number; // 节点进入动画时长 |
| 63 | + nodeExitDuration: number; // 节点退出动画时长 |
| 64 | + edgeFlowDuration: number; // 边流动动画时长 |
| 65 | + annotationDuration: number; // 标注显示时长 |
| 66 | + annotationFadeOut: number; // 标注淡出时长 |
| 67 | + easing: string; // 缓动函数 |
| 68 | +} |
| 69 | + |
| 70 | +// 根据播放速度计算实际动画时长 |
| 71 | +function getScaledDuration(baseDuration: number, speed: number): number { |
| 72 | + return baseDuration / speed; |
| 73 | +} |
| 74 | +``` |
| 75 | + |
| 76 | +### 3. FloatingAnnotation |
| 77 | + |
| 78 | +浮动标注数据结构。 |
| 79 | + |
| 80 | +```typescript |
| 81 | +interface FloatingAnnotation { |
| 82 | + id: string; |
| 83 | + type: 'digit' | 'letter' | 'combination' | 'backtrack'; |
| 84 | + content: string; |
| 85 | + position: { x: number; y: number }; |
| 86 | + nodeId: string; |
| 87 | + createdAt: number; |
| 88 | +} |
| 89 | +``` |
| 90 | + |
| 91 | +## Data Models |
| 92 | + |
| 93 | +### 动画状态映射 |
| 94 | + |
| 95 | +| StepType | 节点动画 | 边动画 | 浮动标注 | |
| 96 | +|----------|---------|--------|---------| |
| 97 | +| INIT | - | - | - | |
| 98 | +| ENTER_RECURSION | 脉冲激活 | - | 显示数字 "处理: {digit}" | |
| 99 | +| SELECT_LETTER | 新节点缩放进入 | 向下流动 | 显示字母 "+{letter}" | |
| 100 | +| ADD_TO_PATH | 路径高亮延伸 | 路径边发光 | - | |
| 101 | +| FOUND_COMBINATION | 成功标记 ✓ | 路径变绿 | 🎉 "{combination}" | |
| 102 | +| BACKTRACK | 淡出变灰 | 向上流动 | ↩ | |
| 103 | +| EXIT_RECURSION | - | - | - | |
| 104 | +| COMPLETE | - | - | 🎊 "完成!" | |
| 105 | + |
| 106 | +### 节点视觉状态 |
| 107 | + |
| 108 | +```typescript |
| 109 | +type NodeVisualState = 'default' | 'active' | 'completed' | 'backtracked' | 'highlighted'; |
| 110 | + |
| 111 | +const NODE_STYLES: Record<NodeVisualState, NodeStyle> = { |
| 112 | + default: { |
| 113 | + fill: '#ffffff', |
| 114 | + stroke: '#4a90d9', |
| 115 | + strokeWidth: 2, |
| 116 | + scale: 1, |
| 117 | + opacity: 1, |
| 118 | + }, |
| 119 | + active: { |
| 120 | + fill: '#4a90d9', |
| 121 | + stroke: '#2c5282', |
| 122 | + strokeWidth: 3, |
| 123 | + scale: 1.1, |
| 124 | + opacity: 1, |
| 125 | + glow: true, |
| 126 | + pulse: true, |
| 127 | + }, |
| 128 | + completed: { |
| 129 | + fill: '#27ae60', |
| 130 | + stroke: '#1e8449', |
| 131 | + strokeWidth: 2, |
| 132 | + scale: 1, |
| 133 | + opacity: 1, |
| 134 | + checkmark: true, |
| 135 | + }, |
| 136 | + backtracked: { |
| 137 | + fill: '#bdc3c7', |
| 138 | + stroke: '#95a5a6', |
| 139 | + strokeWidth: 2, |
| 140 | + scale: 0.9, |
| 141 | + opacity: 0.7, |
| 142 | + }, |
| 143 | + highlighted: { |
| 144 | + fill: '#ffc107', |
| 145 | + stroke: '#e0a800', |
| 146 | + strokeWidth: 3, |
| 147 | + scale: 1.15, |
| 148 | + opacity: 1, |
| 149 | + }, |
| 150 | +}; |
| 151 | +``` |
| 152 | + |
| 153 | +### 边视觉状态 |
| 154 | + |
| 155 | +```typescript |
| 156 | +type EdgeVisualState = 'default' | 'active' | 'completed' | 'backtracked' | 'highlighted'; |
| 157 | + |
| 158 | +const EDGE_STYLES: Record<EdgeVisualState, EdgeStyle> = { |
| 159 | + default: { |
| 160 | + stroke: '#95a5a6', |
| 161 | + strokeWidth: 2, |
| 162 | + dashArray: 'none', |
| 163 | + animation: 'none', |
| 164 | + }, |
| 165 | + active: { |
| 166 | + stroke: '#4a90d9', |
| 167 | + strokeWidth: 3, |
| 168 | + dashArray: '8,4', |
| 169 | + animation: 'flow-down', // 向下流动的虚线动画 |
| 170 | + }, |
| 171 | + completed: { |
| 172 | + stroke: '#27ae60', |
| 173 | + strokeWidth: 3, |
| 174 | + dashArray: 'none', |
| 175 | + animation: 'glow-pulse', // 短暂发光 |
| 176 | + }, |
| 177 | + backtracked: { |
| 178 | + stroke: '#bdc3c7', |
| 179 | + strokeWidth: 2, |
| 180 | + dashArray: '4,4', |
| 181 | + animation: 'flow-up', // 向上流动的虚线动画 |
| 182 | + }, |
| 183 | + highlighted: { |
| 184 | + stroke: '#ffc107', |
| 185 | + strokeWidth: 4, |
| 186 | + dashArray: 'none', |
| 187 | + animation: 'none', |
| 188 | + }, |
| 189 | +}; |
| 190 | +``` |
| 191 | + |
| 192 | +## Correctness Properties |
| 193 | + |
| 194 | +*A property is a characteristic or behavior that should hold true across all valid executions of a system-essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.* |
| 195 | + |
| 196 | +### Property 1: Node Visual State Mapping |
| 197 | + |
| 198 | +*For any* tree node with a given status (isActive, isCompleted, isBacktracked), the rendered node SHALL have the corresponding visual attributes (fill color, stroke, scale, opacity) as defined in NODE_STYLES. |
| 199 | + |
| 200 | +**Validates: Requirements 2.1, 2.2, 2.3, 2.4** |
| 201 | + |
| 202 | +### Property 2: Edge Visual State Mapping |
| 203 | + |
| 204 | +*For any* edge connecting two nodes, the edge SHALL have visual attributes corresponding to the target node's state (active → flow-down animation, backtracked → flow-up animation, completed → green with glow). |
| 205 | + |
| 206 | +**Validates: Requirements 3.1, 3.2, 3.3** |
| 207 | + |
| 208 | +### Property 3: Annotation Content Mapping |
| 209 | + |
| 210 | +*For any* animation step with a given StepType, IF an annotation should be displayed (ENTER_RECURSION, SELECT_LETTER, FOUND_COMBINATION, BACKTRACK), THEN the annotation content SHALL match the expected format for that step type. |
| 211 | + |
| 212 | +**Validates: Requirements 4.1, 4.2, 4.3, 4.4** |
| 213 | + |
| 214 | +### Property 4: Path Highlight Consistency |
| 215 | + |
| 216 | +*For any* current path from root to active node, all nodes and edges along that path SHALL have the highlighted visual state applied. |
| 217 | + |
| 218 | +**Validates: Requirements 5.1, 5.2, 5.3** |
| 219 | + |
| 220 | +### Property 5: Animation Duration Scaling |
| 221 | + |
| 222 | +*For any* playback speed value, the actual animation duration SHALL equal the base duration divided by the speed factor. |
| 223 | + |
| 224 | +**Validates: Requirements 6.3** |
| 225 | + |
| 226 | +## Animation Storyboard (动画分镜设计) |
| 227 | + |
| 228 | +### 场景 1: 进入递归 (ENTER_RECURSION) |
| 229 | + |
| 230 | +``` |
| 231 | +时间轴: 0ms ──────────────────────────────────────── 600ms |
| 232 | + │ │ |
| 233 | + ├─ 0-200ms: 当前节点脉冲放大 (scale 1→1.1) |
| 234 | + │ 发光效果渐显 |
| 235 | + │ |
| 236 | + ├─ 100-400ms: 浮动标注淡入 |
| 237 | + │ 内容: "处理: {digit}" |
| 238 | + │ 位置: 节点上方 |
| 239 | + │ |
| 240 | + └─ 400-600ms: 标注保持显示 |
| 241 | +``` |
| 242 | + |
| 243 | +### 场景 2: 选择字母 (SELECT_LETTER) |
| 244 | + |
| 245 | +``` |
| 246 | +时间轴: 0ms ──────────────────────────────────────── 800ms |
| 247 | + │ │ |
| 248 | + ├─ 0-300ms: 新节点从父节点位置 |
| 249 | + │ 缩放淡入 (scale 0→1, opacity 0→1) |
| 250 | + │ |
| 251 | + ├─ 0-400ms: 连接边绘制动画 |
| 252 | + │ 从父节点向子节点延伸 |
| 253 | + │ 虚线向下流动效果 |
| 254 | + │ |
| 255 | + ├─ 200-500ms: 字母标注淡入 |
| 256 | + │ 内容: "+{letter}" |
| 257 | + │ 位置: 新节点上方 |
| 258 | + │ |
| 259 | + └─ 300-800ms: 父节点脉冲减弱 |
| 260 | + 新节点成为活动节点 |
| 261 | +``` |
| 262 | + |
| 263 | +### 场景 3: 找到组合 (FOUND_COMBINATION) |
| 264 | + |
| 265 | +``` |
| 266 | +时间轴: 0ms ──────────────────────────────────────── 1200ms |
| 267 | + │ │ |
| 268 | + ├─ 0-200ms: 当前节点放大 (scale 1→1.2) |
| 269 | + │ |
| 270 | + ├─ 100-400ms: 路径上所有边变绿 |
| 271 | + │ 短暂发光效果 |
| 272 | + │ |
| 273 | + ├─ 200-500ms: 节点内显示 ✓ 图标 |
| 274 | + │ |
| 275 | + ├─ 300-800ms: 庆祝标注淡入 |
| 276 | + │ 内容: "🎉 {combination}" |
| 277 | + │ 位置: 节点上方,稍大字体 |
| 278 | + │ |
| 279 | + └─ 800-1200ms: 标注淡出 |
| 280 | + 节点恢复正常大小 |
| 281 | +``` |
| 282 | + |
| 283 | +### 场景 4: 回溯 (BACKTRACK) |
| 284 | + |
| 285 | +``` |
| 286 | +时间轴: 0ms ──────────────────────────────────────── 600ms |
| 287 | + │ │ |
| 288 | + ├─ 0-150ms: 回溯图标淡入 |
| 289 | + │ 内容: "↩" |
| 290 | + │ 位置: 当前节点旁 |
| 291 | + │ |
| 292 | + ├─ 100-400ms: 当前节点淡出变灰 |
| 293 | + │ (opacity 1→0.7, scale 1→0.9) |
| 294 | + │ |
| 295 | + ├─ 150-450ms: 连接边向上流动动画 |
| 296 | + │ 颜色变为灰色 |
| 297 | + │ |
| 298 | + └─ 300-600ms: 父节点重新激活 |
| 299 | + 脉冲效果 |
| 300 | +``` |
| 301 | + |
| 302 | +### 场景 5: 算法完成 (COMPLETE) |
| 303 | + |
| 304 | +``` |
| 305 | +时间轴: 0ms ──────────────────────────────────────── 1500ms |
| 306 | + │ │ |
| 307 | + ├─ 0-500ms: 所有完成路径依次高亮 |
| 308 | + │ 波浪式发光效果 |
| 309 | + │ |
| 310 | + ├─ 500-1000ms: 中央显示完成标注 |
| 311 | + │ 内容: "🎊 算法完成!" |
| 312 | + │ 带有缩放弹跳效果 |
| 313 | + │ |
| 314 | + └─ 1000-1500ms: 标注淡出 |
| 315 | + 树恢复静态显示 |
| 316 | +``` |
| 317 | + |
| 318 | +## Error Handling |
| 319 | + |
| 320 | +1. **空树状态**: 当 treeData 为 null 时,显示占位提示 "输入数字后开始演示" |
| 321 | +2. **动画中断**: 当用户快速切换步骤时,取消进行中的动画,立即跳转到目标状态 |
| 322 | +3. **性能保护**: 当节点数量超过 100 时,简化动画效果(禁用粒子效果、减少发光) |
| 323 | +4. **标注堆叠**: 当多个标注同时显示时,自动调整位置避免重叠 |
| 324 | + |
| 325 | +## Testing Strategy |
| 326 | + |
| 327 | +### Unit Tests |
| 328 | + |
| 329 | +1. **getNodeVisualState**: 测试节点状态到视觉状态的映射 |
| 330 | +2. **getEdgeVisualState**: 测试边状态到视觉状态的映射 |
| 331 | +3. **getAnnotationContent**: 测试步骤类型到标注内容的映射 |
| 332 | +4. **getScaledDuration**: 测试动画时长缩放计算 |
| 333 | + |
| 334 | +### Property-Based Tests |
| 335 | + |
| 336 | +使用 fast-check 库进行属性测试: |
| 337 | + |
| 338 | +1. **Property 1 测试**: 生成随机节点状态组合,验证视觉属性映射正确 |
| 339 | +2. **Property 2 测试**: 生成随机边状态,验证边样式映射正确 |
| 340 | +3. **Property 3 测试**: 生成随机步骤类型,验证标注内容格式正确 |
| 341 | +4. **Property 4 测试**: 生成随机路径,验证路径高亮一致性 |
| 342 | +5. **Property 5 测试**: 生成随机播放速度,验证动画时长缩放正确 |
| 343 | + |
| 344 | +### Integration Tests |
| 345 | + |
| 346 | +1. 验证 AlgorithmGuide 组件已从 App.tsx 中移除 |
| 347 | +2. 验证 TreeVisualization 组件正确接收新的 props |
| 348 | +3. 验证动画在不同播放速度下正常工作 |
| 349 | + |
0 commit comments