- 新增 _calculate_lane_conflicts_v2() 分開返回標籤重疊和線穿框分數 - 修改泳道選擇算法,優先選擇無標籤重疊的泳道 - 兩階段搜尋:優先側別無可用泳道則嘗試另一側 - 增強日誌輸出,顯示標籤範圍和詳細衝突分數 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
8.6 KiB
8.6 KiB
時間軸標籤避碰改進(v5.0) - 真正的碰撞預防系統
新增改進(v5.0)
核心概念:從靜態分層到動態碰撞檢測
v4.1 的問題:
- ❌ 只是根據層級靜態計算路徑高度
- ❌ 沒有真正檢測線條之間的碰撞
- ❌ 沒有檢測線條與文字框的碰撞
- ❌ 仍然會出現嚴重的重疊
v5.0 的解決方案:
- ✅ 真正的碰撞檢測算法
- ✅ 動態路徑優化
- ✅ 20個候選高度,選擇最佳路徑
- ✅ 實時追蹤已繪製的線條和文字框
技術實現
1. 碰撞檢測算法
線段與線段碰撞檢測
def check_collision(x_start_sec, x_end_sec, y_height, margin=0.05):
collision_score = 0
# 檢查與已繪製線段的碰撞
for seg_start, seg_end, seg_y in drawn_horizontal_segments:
# Y 座標是否接近(在 margin 範圍內)
if abs(y_height - seg_y) < margin:
# X 範圍是否重疊
if not (x_end_sec < seg_start or x_start_sec > seg_end):
overlap = min(x_end_sec, seg_end) - max(x_start_sec, seg_start)
collision_score += overlap / (x_end_sec - x_start_sec + 1)
return collision_score
邏輯:
- 檢查新線段的水平部分是否與已有線段在同一高度(±5%範圍內)
- 計算 X 軸重疊的比例
- 重疊越多,碰撞分數越高
線段與文字框碰撞檢測
# 檢查與文字框的碰撞
for box_x, box_y, box_w, box_h in text_boxes:
# Y 座標是否在文字框範圍內
if abs(y_height - box_y) < box_h / 2 + margin:
# X 範圍是否穿過文字框
box_left = box_x - box_w / 2
box_right = box_x + box_w / 2
if not (x_end_sec < box_left or x_start_sec > box_right):
overlap = min(x_end_sec, box_right) - max(x_start_sec, box_left)
collision_score += overlap / (x_end_sec - x_start_sec + 1) * 2 # 權重 x2
邏輯:
- 檢查線段是否穿過文字框的垂直範圍
- 計算與文字框的 X 軸重疊
- 文字框碰撞的權重是線段碰撞的2倍(更嚴重)
2. 最佳路徑選擇
def find_best_path_height(event_x_sec, label_x_sec, label_y, layer):
is_upper = label_y > 0
# 生成20個候選高度
candidates = []
if is_upper:
# 上方:從 20% 到 90% (每次增加 3.5%)
for i in range(20):
ratio = 0.20 + (i * 0.035)
candidates.append(ratio)
else:
# 下方:從 90% 到 20% (每次減少 3.5%)
for i in range(20):
ratio = 0.90 - (i * 0.035)
candidates.append(ratio)
# 計算每個高度的碰撞分數
best_ratio = candidates[layer % len(candidates)] # 默認值
min_collision = float('inf')
x_start = min(event_x_sec, label_x_sec)
x_end = max(event_x_sec, label_x_sec)
for ratio in candidates:
test_y = label_y * ratio
score = check_collision(x_start, x_end, test_y)
if score < min_collision:
min_collision = score
best_ratio = ratio
return best_ratio
邏輯:
- 根據標籤位置(上方/下方)生成20個候選高度
- 對每個候選高度計算碰撞分數
- 選擇碰撞分數最低的高度
- 如果所有高度碰撞分數相同(都是0),使用層級對應的默認高度
3. 實時追蹤系統
# 初始化追蹤列表
drawn_horizontal_segments = [] # [(x_start, x_end, y), ...]
text_boxes = [] # [(x_center, y_center, width, height), ...]
# 繪製後記錄
if not is_directly_above:
drawn_horizontal_segments.append((x_start_sec, x_end_sec, mid_y))
text_boxes.append((label_x_sec, label_y, label_width_sec, label_height))
效果:
- 每繪製一條線段,立即記錄其位置
- 每繪製一個文字框,立即記錄其範圍
- 後續線條會避開已記錄的所有障礙物
效果對比
v4.1(靜態分層)
# 只根據層級計算高度
if is_upper_side:
base_ratio = 0.25
layer_offset = layer_group * 0.06
mid_y_ratio = base_ratio + layer_offset
❌ 問題:無法知道這個高度是否會碰撞
v5.0(動態碰撞檢測)
# 測試20個候選高度
for ratio in candidates:
test_y = label_y * ratio
score = check_collision(x_start, x_end, test_y)
if score < min_collision:
best_ratio = ratio
✅ 優勢:保證選擇碰撞最少的路徑
性能分析
時間複雜度
- 單條線路徑選擇:O(候選數 × (已繪線段數 + 文字框數))
- 全部線條:O(事件數 × 候選數 × 事件數) = O(20n²)
- 實際情況:因為是按順序繪製,平均複雜度約為 O(10n²)
空間複雜度
- 線段追蹤:O(事件數)
- 文字框追蹤:O(事件數)
- 總計:O(事件數)
性能表現
- 10 個事件:~2000 次碰撞檢測
- 50 個事件:~50000 次碰撞檢測
- 100 個事件:~200000 次碰撞檢測
優化空間:
- 可以使用空間索引(R-tree)降低到 O(n log n)
- 可以減少候選數量(從20降到10)
- 可以使用啟發式策略減少檢測次數
參數配置
# 碰撞檢測參數
margin = 0.05 # Y 軸碰撞容忍度(5%)
text_box_weight = 2.0 # 文字框碰撞權重(x2)
# 候選高度參數
candidates_count = 20 # 候選高度數量
upper_range = (0.20, 0.90) # 上方高度範圍 20%-90%
lower_range = (0.90, 0.20) # 下方高度範圍 90%-20%
step = 0.035 # 每次增減 3.5%
# 文字框估算參數
label_width_ratio = 0.15 # 文字框寬度 = 15% 時間軸
label_height = 0.3 # 文字框高度 = 0.3 單位
調整建議
如果仍有碰撞
- 增加候選高度數量
for i in range(30): # 從 20 增加到 30
ratio = 0.20 + (i * 0.024) # 調整步長
- 增加碰撞容忍度
margin = 0.08 # 從 0.05 增加到 0.08
- 增加文字框尺寸估算
label_width_sec = time_range_seconds * 0.18 # 從 0.15 增加到 0.18
label_height = 0.4 # 從 0.3 增加到 0.4
如果性能太慢
- 減少候選數量
for i in range(10): # 從 20 減少到 10
- 使用啟發式優先級
# 優先測試層級對應的高度附近的候選
priority_candidates = [
candidates[layer % len(candidates)], # 優先級1:層級對應
candidates[(layer-1) % len(candidates)], # 優先級2:相鄰
candidates[(layer+1) % len(candidates)], # 優先級3:相鄰
# ... 然後測試其他候選
]
視覺效果
碰撞檢測過程示意
測試候選高度 ratio=0.20 (20%):
████████████ 線段1 (已存在)
────────────────── 測試線段 ← 碰撞! score=0.8
測試候選高度 ratio=0.35 (35%):
────────────────── 測試線段 ← 無碰撞! score=0.0 ✓
████████████ 線段1 (已存在)
選擇 ratio=0.35,碰撞分數最低
文字框避讓示意
┌──────────┐
│ 文字框A │ (已存在)
└──────────┘
↓
────────── 測試路徑1 ← 穿過文字框! score=1.5
↓
─────────── 測試路徑2 ← 避開文字框! score=0.0 ✓
●
時間軸
版本改進總結
| 版本 | 方法 | 線條交錯 | 線條穿框 | 性能 |
|---|---|---|---|---|
| v3.2 | 增加間距 | ❌ 嚴重 | ❌ 嚴重 | ⚡ 快 |
| v4.0 | 層級偏移 | ⚠️ 存在 | ⚠️ 偶爾 | ⚡ 快 |
| v4.1 | 鏡像分布 | ⚠️ 仍有 | ⚠️ 仍有 | ⚡ 快 |
| v5.0 | 碰撞檢測 | ✅ 最小 | ✅ 極少 | ⚡ 中等 |
未來改進方向
1. 空間索引優化
使用 R-tree 或 KD-tree 加速碰撞檢測:
- 當前:O(n) 檢測每個障礙物
- 優化後:O(log n) 查詢相關障礙物
2. 貝茲曲線
使用平滑曲線代替直角折線:
- 更自然的視覺效果
- 更容易避開障礙物
3. A 路徑規劃*
使用圖搜索算法找到最優路徑:
- 可以繞過複雜的障礙物布局
- 保證找到全局最優解
4. 分組優化
對事件進行分組,組內使用相似的路徑高度:
- 減少視覺混亂
- 突出事件的邏輯關係
版本: v5.0 更新日期: 2025-11-05 作者: Claude AI
關鍵突破
從靜態規則到動態智能:
- v1-v4:根據規則計算路徑 → 希望不會碰撞
- v5:測試所有可能路徑 → 保證選擇最佳路徑
這是從被動避讓到主動檢測的質的飛躍! 🚀