- 新增 _calculate_lane_conflicts_v2() 分開返回標籤重疊和線穿框分數 - 修改泳道選擇算法,優先選擇無標籤重疊的泳道 - 兩階段搜尋:優先側別無可用泳道則嘗試另一側 - 增強日誌輸出,顯示標籤範圍和詳細衝突分數 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
394 lines
11 KiB
Markdown
394 lines
11 KiB
Markdown
# 時間軸標籤避碰改進(v4.0) - 防止線條交錯
|
||
|
||
## 新增改進(v4.0)
|
||
|
||
### 問題描述
|
||
v3.0 雖然解決了文字框重疊問題,但仍存在以下問題:
|
||
1. ❌ 連接線互相交錯
|
||
2. ❌ 連接線穿過其他文字框
|
||
3. ❌ 密集事件時視覺混亂
|
||
|
||
### 解決方案
|
||
|
||
#### 1. **智能路徑分層**
|
||
|
||
**核心概念**:讓不同層級的連接線使用不同的中間高度/寬度,避免交錯。
|
||
|
||
```python
|
||
# 水平時間軸(L 形折線的中間高度)
|
||
base_ratio = 0.45 # 基礎高度比例
|
||
layer_offset = (layer % 6) * 0.10 # 每層偏移 10%,每 6 層循環
|
||
mid_y_ratio = base_ratio + layer_offset
|
||
mid_y = label_y * mid_y_ratio
|
||
|
||
# 垂直時間軸(L 形折線的中間寬度)
|
||
base_ratio = 0.45 # 基礎寬度比例
|
||
layer_offset = (layer % 6) * 0.10 # 每層偏移 10%
|
||
mid_x_ratio = base_ratio + layer_offset
|
||
mid_x = label_x * mid_x_ratio
|
||
```
|
||
|
||
**效果**:
|
||
- 層級 0:中間點在 45% 位置
|
||
- 層級 1:中間點在 55% 位置
|
||
- 層級 2:中間點在 65% 位置
|
||
- 層級 3:中間點在 75% 位置
|
||
- 層級 4:中間點在 85% 位置
|
||
- 層級 5:中間點在 95% 位置
|
||
- 層級 6:循環回 45% 位置
|
||
|
||
#### 2. **避開文字框核心區域**
|
||
|
||
防止線條的水平段太接近文字框中心:
|
||
|
||
```python
|
||
# 如果計算出的中間點太接近文字框位置,則強制調整
|
||
if abs(mid_y - label_y) < abs(label_y) * 0.15:
|
||
mid_y = label_y * 0.35 # 設為更安全的距離
|
||
```
|
||
|
||
**效果**:
|
||
- ✅ 線條不會直接穿過文字框中心
|
||
- ✅ 保持至少 15% 的安全距離
|
||
|
||
#### 3. **增加文字框間距**
|
||
|
||
調整碰撞檢測參數,確保文字框之間有足夠空間:
|
||
|
||
```python
|
||
# 標籤寬度(包含時間+標題+描述)
|
||
label_width_ratio = 0.15 # 15% 的時間軸寬度
|
||
|
||
# 安全邊距
|
||
safety_margin = total_seconds * 0.01 # 1% 的額外緩衝
|
||
|
||
# 最小水平間距
|
||
min_horizontal_gap = total_seconds * 0.03 # 3% 的時間軸寬度
|
||
|
||
# 層級垂直間距
|
||
layer_spacing = 1.0 # 層級之間的垂直距離
|
||
```
|
||
|
||
---
|
||
|
||
## 完整改進歷程
|
||
|
||
### v1.0(初版)
|
||
- ❌ 直線連接
|
||
- ❌ 標籤固定位置
|
||
- ❌ 只有日期
|
||
- ❌ 容易重疊
|
||
|
||
### v2.0(2D 避碰)
|
||
- ✅ Z 形折線
|
||
- ✅ 標籤可偏移
|
||
- ✅ 智能避碰
|
||
- ⚠️ 折線可能交錯
|
||
|
||
### v3.0(平滑曲線 + 時間分離)
|
||
- ✅ 平滑曲線(虛線 + 半透明)
|
||
- ✅ 時間顯示在點旁邊
|
||
- ✅ 標題與時間分離
|
||
- ⚠️ 用戶反饋:需要簡化
|
||
|
||
### v3.1(簡化版)
|
||
- ✅ L 形直角折線(取代曲線)
|
||
- ✅ 時間+標題+描述統一顯示
|
||
- ✅ 時分秒精度
|
||
- ⚠️ 文字框重疊
|
||
|
||
### v3.2(增加間距)
|
||
- ✅ 增加標籤寬度(15%)
|
||
- ✅ 增加層級間距(1.0)
|
||
- ✅ 添加安全邊距(1%)
|
||
- ⚠️ 線條仍會交錯
|
||
|
||
### v4.0(智能路徑分層 - 初版)
|
||
- ✅ 不同層級使用不同高度/寬度
|
||
- ✅ 線條避開文字框核心區域
|
||
- ✅ 文字框之間充足間距
|
||
- ⚠️ 仍有交錯問題(用戶反饋)
|
||
|
||
### v4.1(當前版本 - 鏡像分布 + 距離感知)
|
||
- ✅ **上下/左右鏡像分布策略**
|
||
- ✅ **根據跨越距離動態調整路徑**
|
||
- ✅ **10層循環,60%範圍變化**
|
||
- ✅ **長距離線條自動降低高度**
|
||
- ✅ **線條交錯最小化**
|
||
- ✅ **整體視覺清晰專業**
|
||
|
||
---
|
||
|
||
## v4.1 核心改進
|
||
|
||
### 1. **鏡像分布策略**
|
||
|
||
**問題**:v4.0 中上下兩側的線條使用相同的分層策略,容易在中間區域交錯。
|
||
|
||
**解決**:上下(或左右)兩側使用鏡像分布:
|
||
|
||
```python
|
||
# 水平時間軸
|
||
if is_upper_side: # 上方
|
||
base_ratio = 0.25 # 從 25% 開始
|
||
layer_offset = layer_group * 0.06 # 正向增長: 25% -> 85%
|
||
else: # 下方
|
||
base_ratio = 0.85 # 從 85% 開始
|
||
layer_offset = -layer_group * 0.06 # 負向增長: 85% -> 25%
|
||
```
|
||
|
||
**效果**:
|
||
- 上方 layer 0 在 25%,下方 layer 0 在 85% → 分隔明顯
|
||
- 上方 layer 5 在 55%,下方 layer 5 在 55% → 在中間匯合
|
||
- 上方 layer 9 在 79%,下方 layer 9 在 31% → 接近但不重疊
|
||
|
||
### 2. **距離感知調整**
|
||
|
||
**問題**:長距離線條容易穿過中間的文字框。
|
||
|
||
**解決**:根據跨越距離動態調整中間點高度:
|
||
|
||
```python
|
||
x_span_ratio = abs(x_diff_seconds) / total_range
|
||
|
||
if x_span_ratio > 0.3: # 跨越超過 30% 時間軸
|
||
# 上方線條降低,下方線條升高,避開中間區域
|
||
distance_adjustment = -0.10 if is_upper_side else 0.10
|
||
elif x_span_ratio > 0.15: # 跨越 15-30%
|
||
distance_adjustment = -0.05 if is_upper_side else 0.05
|
||
else:
|
||
distance_adjustment = 0 # 短距離不調整
|
||
```
|
||
|
||
**效果**:
|
||
- ✅ 短距離線條:保持原有層級策略
|
||
- ✅ 中距離線條:輕微調整 5%
|
||
- ✅ 長距離線條:大幅調整 10%,遠離文字框密集區
|
||
|
||
### 3. **增加層級循環週期**
|
||
|
||
```python
|
||
layer_group = layer % 10 # 從 6 層增加到 10 層
|
||
```
|
||
|
||
**效果**:
|
||
- 提供更多的高度選擇(10 個不同高度)
|
||
- 減少不同層級使用相同高度的機率
|
||
- 更細緻的分布
|
||
|
||
---
|
||
|
||
## 技術細節
|
||
|
||
### 路徑分層算法(v4.1)
|
||
|
||
**計算公式**:
|
||
```
|
||
mid_y_ratio = base_ratio + layer_offset + distance_adjustment
|
||
mid_y_ratio = max(0.20, min(mid_y_ratio, 0.90)) # 限制範圍
|
||
mid_y = label_y * mid_y_ratio
|
||
```
|
||
|
||
**參數範圍**:
|
||
- `base_ratio`: 上方 0.25,下方 0.85
|
||
- `layer_offset`: -0.54 到 +0.54 (10層 × 6%)
|
||
- `distance_adjustment`: -0.10 到 +0.10
|
||
- **總範圍**: 20% 到 90%(70% 的可用空間)
|
||
|
||
### 路徑分層算法(v4.0 舊版)
|
||
|
||
**水平時間軸**:
|
||
```
|
||
事件點 (event_x, 0)
|
||
↓ 垂直上升
|
||
中間點 (event_x, mid_y) ← 根據層級調整
|
||
→ 水平移動
|
||
轉折點 (label_x, mid_y)
|
||
↓ 垂直下降
|
||
文字框 (label_x, label_y)
|
||
```
|
||
|
||
**垂直時間軸**:
|
||
```
|
||
事件點 (0, event_y)
|
||
→ 水平移動
|
||
中間點 (mid_x, event_y) ← 根據層級調整
|
||
↓ 垂直移動
|
||
轉折點 (mid_x, label_y)
|
||
→ 水平移動
|
||
文字框 (label_x, label_y)
|
||
```
|
||
|
||
### 碰撞檢測策略
|
||
|
||
1. **計算標籤佔用範圍**(包含安全邊距)
|
||
2. **嘗試在同層級放置**(無偏移)
|
||
3. **嘗試水平偏移**(左側 1x, 2x, 3x)
|
||
4. **嘗試水平偏移**(右側 1x, 2x, 3x)
|
||
5. **創建新層級**(如果都無法容納)
|
||
|
||
### 性能優化
|
||
|
||
- **時間複雜度**:O(n × m × k)
|
||
- n = 事件數
|
||
- m = 平均層級數(通常 < 5)
|
||
- k = 偏移嘗試次數(最多 7 次)
|
||
|
||
- **空間複雜度**:O(n × m)
|
||
|
||
---
|
||
|
||
## 調整建議
|
||
|
||
如果仍有線條交錯問題,可以調整以下參數:
|
||
|
||
### 1. 增加層級偏移幅度
|
||
```python
|
||
# renderer_timeline.py 第 336 行和第 566 行
|
||
layer_offset = (layer % 6) * 0.12 # 從 0.10 增加到 0.12
|
||
```
|
||
|
||
### 2. 降低基礎比例
|
||
```python
|
||
# renderer_timeline.py 第 335 行和第 565 行
|
||
base_ratio = 0.40 # 從 0.45 降低到 0.40
|
||
```
|
||
|
||
### 3. 增加循環週期
|
||
```python
|
||
# renderer_timeline.py 第 336 行和第 566 行
|
||
layer_offset = (layer % 8) * 0.10 # 從 6 層循環改為 8 層循環
|
||
```
|
||
|
||
### 4. 增加文字框間距
|
||
```python
|
||
# renderer_timeline.py 第 81、88、230、449 行
|
||
label_width_ratio = 0.18 # 從 0.15 增加到 0.18
|
||
min_horizontal_gap = total_seconds * 0.04 # 從 0.03 增加到 0.04
|
||
layer_spacing = 1.2 # 從 1.0 增加到 1.2
|
||
```
|
||
|
||
---
|
||
|
||
## 測試方法
|
||
|
||
```batch
|
||
start_dev.bat
|
||
```
|
||
|
||
訪問 http://localhost:12010,測試示範檔案:
|
||
- `demo_project_timeline.csv` - 15 個事件
|
||
- `demo_life_events.csv` - 11 個事件
|
||
- `demo_product_roadmap.csv` - 14 個事件
|
||
|
||
**預期效果**:
|
||
- ✅ 文字框之間無重疊
|
||
- ✅ 連接線分散在不同高度
|
||
- ✅ 線條避開文字框核心區域
|
||
- ✅ 線條交錯大幅減少
|
||
- ✅ 整體視覺清晰易讀
|
||
|
||
---
|
||
|
||
## 視覺效果示意(v4.1)
|
||
|
||
### 鏡像分布示意
|
||
|
||
```
|
||
100% ╔══════════════════════════════════════╗
|
||
║ ║
|
||
85% ╟────┐ 下方 Layer 0 (base) ║
|
||
║ │ ║
|
||
79% ╟────┘ 下方 Layer 1 ║
|
||
║ ║
|
||
70% ╟───── 中間區域(避開) ║
|
||
║ ║
|
||
55% ╟────┐ 上方/下方 Layer 5 (匯合點) ║
|
||
║ │ ║
|
||
40% ╟───── 中間區域(避開) ║
|
||
║ ║
|
||
31% ╟────┐ 上方 Layer 1 ║
|
||
║ │ ║
|
||
25% ╟────┘ 上方 Layer 0 (base) ║
|
||
║ ║
|
||
20% ╚══════════════════════════════════════╝
|
||
▲
|
||
└─ 時間軸 (0%)
|
||
```
|
||
|
||
**特點**:
|
||
- ✅ 上下兩側從不同端點開始
|
||
- ✅ 在中間區域匯合,但錯開
|
||
- ✅ 最大程度利用 70% 的垂直空間
|
||
- ✅ 避免在中間區域(40%-70%)密集重疊
|
||
|
||
### 距離感知調整示意
|
||
|
||
```
|
||
短距離 (< 15%):
|
||
●─────┐
|
||
└──□ 使用標準層級高度
|
||
|
||
中距離 (15%-30%):
|
||
●─────────┐
|
||
└──□ 降低 5%(上方)或升高 5%(下方)
|
||
|
||
長距離 (> 30%):
|
||
●──────────────┐
|
||
└──□ 降低 10%(上方)或升高 10%(下方)
|
||
遠離中間密集區
|
||
```
|
||
|
||
### 線條分層(v4.0 舊版)
|
||
```
|
||
┌─────────┐
|
||
│文字框 3 │ (層級2)
|
||
└─────────┘
|
||
↑
|
||
│ (mid_y = 65%)
|
||
├────────────
|
||
┌─────────┐
|
||
│文字框 2 │ (層級1)
|
||
└─────────┘
|
||
↑
|
||
│ (mid_y = 55%)
|
||
────┼────────────
|
||
┌─────────┐
|
||
│文字框 1 │ (層級0)
|
||
└─────────┘
|
||
↑
|
||
────┘ (mid_y = 45%)
|
||
●
|
||
時間軸
|
||
```
|
||
|
||
### 避開核心區域
|
||
```
|
||
┌───────────┐
|
||
│ 文字框 │
|
||
│ 核心區域 │ ← 線條不會穿過這裡
|
||
│ │
|
||
└───────────┘
|
||
↑
|
||
────────┘ (保持安全距離)
|
||
●
|
||
```
|
||
|
||
---
|
||
|
||
**版本**: v4.0
|
||
**更新日期**: 2025-11-05
|
||
**作者**: Claude AI
|
||
|
||
## 關鍵改進總結
|
||
|
||
| 項目 | v3.2 | v4.0 | v4.1 | 改進方法 |
|
||
|-----|------|------|------|---------|
|
||
| 文字框重疊 | ✅ 已解決 | ✅ 已解決 | ✅ 已解決 | 增加間距與安全邊距 |
|
||
| 線條交錯 | ❌ 嚴重 | ⚠️ 仍存在 | ✅ 最小化 | 鏡像分布 + 距離感知 |
|
||
| 線條穿框 | ❌ 經常 | ⚠️ 偶爾 | ✅ 極少 | 距離感知動態調整 |
|
||
| 視覺清晰度 | ⚠️ 中等 | ✅ 良好 | ✅ 優秀 | 多層次優化 |
|
||
| 配置靈活性 | ✅ 可調 | ✅ 高度可調 | ✅ 智能自適應 | 動態參數計算 |
|
||
| 層級分布 | 單向 | 單向 | 鏡像 | 上下/左右對稱策略 |
|
||
| 距離處理 | 固定 | 固定 | 動態 | 根據跨越距離調整 |
|