diff --git a/D3_FORCE_IMPLEMENTATION_COMPLETE.md b/D3_FORCE_IMPLEMENTATION_COMPLETE.md deleted file mode 100644 index 54a92d3..0000000 --- a/D3_FORCE_IMPLEMENTATION_COMPLETE.md +++ /dev/null @@ -1,269 +0,0 @@ -# ✅ D3 Force-Directed Layout 實施完成 - -## 🎉 實施成果 - -成功將時間軸標籤避讓邏輯從 **後端 Plotly** 遷移到 **前端 D3.js Force-Directed Layout**! - ---- - -## 📦 已完成的任務 - -### ✅ 1. 安裝 D3.js 依賴 -- 已安裝 `d3` 和 `@types/d3` -- 68 個新套件,無安全漏洞 - -### ✅ 2. 修改後端 API -- 新增端點:`GET /api/events/raw` -- 返回未經處理的原始事件資料(JSON格式) -- 供前端 D3.js 使用 - -**檔案**: `backend/main.py` (第 159-185 行) - -### ✅ 3. 創建 D3Timeline 組件 -- 檔案:`frontend-react/src/components/D3Timeline.tsx` -- 實現完整的 D3 Force-Directed Layout -- 支持: - - 事件點固定位置(保證時間準確性) - - 標籤動態避碰(碰撞力 + 連結力) - - X軸限制偏移(最大 ±80px) - - Y軸範圍限制 - -### ✅ 4. 修改 API 客戶端 -- 新增方法:`timelineAPI.getRawEvents()` -- 檔案:`frontend-react/src/api/timeline.ts` (第 30-34 行) - -### ✅ 5. 整合到 App.tsx -- 新增渲染模式切換(D3 / Plotly) -- D3 模式默認啟用 -- 保留 Plotly 作為備選 -- 視覺化切換按鈕 - -### ✅ 6. 編譯前端 -- 編譯成功 -- Build 時間:32.54秒 -- 生成檔案大小:5.27 MB(包含 Plotly + D3) - ---- - -## 🎯 D3 Force 技術特性 - -### 1. 固定事件點位置 -```typescript -{ - fx: eventX, // 固定 X - 保證時間準確性 ✅ - fy: axisY, // 固定 Y - 在時間軸上 ✅ -} -``` - -### 2. 五種力的組合 -```typescript -// 1. 碰撞力 - 標籤互相推開 -.force('collide', d3.forceCollide() - .radius(d => Math.max(d.labelWidth / 2, d.labelHeight / 2) + 10) - .strength(0.8) -) - -// 2. 連結力 - 標籤拉向事件點(彈簧) -.force('link', d3.forceLink(links) - .distance(100) - .strength(0.3) -) - -// 3. X方向力 - 保持靠近事件點X座標 -.force('x', d3.forceX(eventX).strength(0.5)) - -// 4. Y方向力 - 保持在上/下方 -.force('y', d3.forceY(initialY).strength(0.3)) - -// 5. tick事件 - 限制範圍 -.on('tick', () => { - // 限制 X 偏移 ±80px - // 限制 Y 範圍 20 ~ innerHeight-20 -}) -``` - -### 3. 智能碰撞檢測 -- 考慮文字框實際尺寸(寬度/高度) -- 使用橢圓碰撞半徑 -- 事件點不參與碰撞(固定位置) - ---- - -## 🚀 測試步驟 - -### 1. 啟動應用程式 -```bash -python app.py -``` - -應用會自動: -- 啟動後端 API (http://localhost:8000) -- 啟動前端服務(React) -- 開啟 PyWebView GUI 視窗 - -### 2. 導入測試資料 -使用以下任一demo檔案: -- `demo_project_timeline.csv` - 15 個事件 -- `demo_life_events.csv` - 11 個事件 -- `demo_product_roadmap.csv` - 14 個事件 - -### 3. 選擇渲染模式 -- **🚀 D3 Force(新版 - 智能避碰)** ← 默認選擇 -- 📊 Plotly(舊版) - -### 4. 點擊「生成時間軸」 - -### 5. 觀察效果 - -**D3 Force 渲染特點**: -- ✅ 標籤自動分散(避免重疊) -- ✅ 事件點位置固定(時間準確) -- ✅ 連接線自然(彈簧效果) -- ✅ 動態模擬過程(可見標籤調整) -- ✅ 自動達到平衡狀態 - -**對比 Plotly 渲染**: -- 點擊「📊 Plotly(舊版)」 -- 重新生成時間軸 -- 對比兩種渲染效果 - ---- - -## 📊 效果對比 - -| 項目 | Plotly 後端 | D3 Force 前端 | -|------|------------|---------------| -| **標籤避讓** | ⚠️ 泳道分配(固定) | ✅ 力導向(動態) | -| **碰撞處理** | ❌ 仍可能重疊 | ✅ 專業避碰 | -| **時間準確性** | ✅ 準確 | ✅ 準確(固定X座標) | -| **視覺效果** | ⚠️ 規律但擁擠 | ✅ 自然分散 | -| **動態調整** | ❌ 需重新渲染 | ✅ 即時模擬 | -| **性能** | ⚠️ 後端計算 | ✅ 瀏覽器端 | -| **可定制性** | ❌ 有限 | ✅ 完全控制 | - ---- - -## 🔧 調整參數(可選) - -如果需要調整 D3 Force 的行為,可編輯 `D3Timeline.tsx`: - -```typescript -// 調整碰撞半徑 -.force('collide', d3.forceCollide() - .radius(d => Math.max(d.labelWidth / 2, d.labelHeight / 2) + 20) // 改為 20 - .strength(0.9) // 改為 0.9 -) - -// 調整彈簧距離 -.force('link', d3.forceLink(links) - .distance(150) // 改為 150(拉得更遠) - .strength(0.2) // 改為 0.2(彈簧較軟) -) - -// 調整 X 偏移限制 -const maxOffset = 120; // 改為 120px -``` - ---- - -## 📁 修改的檔案清單 - -### 後端 -1. `backend/main.py` - 新增 `/api/events/raw` 端點 - -### 前端 -1. `frontend-react/package.json` - 新增 D3 依賴 -2. `frontend-react/src/components/D3Timeline.tsx` - **新建** D3 組件 -3. `frontend-react/src/api/timeline.ts` - 新增 `getRawEvents()` 方法 -4. `frontend-react/src/App.tsx` - 整合 D3Timeline 並添加模式切換 - -### 文檔 -1. `MIGRATION_TO_D3_FORCE.md` - 遷移計劃文檔 -2. `D3_FORCE_IMPLEMENTATION_COMPLETE.md` - 本文件(實施完成報告) - ---- - -## 🎓 技術學習 - -### D3 Force-Directed Layout 原理 -這是一個基於物理模擬的布局算法: - -1. **節點(Nodes)**:事件點 + 標籤 -2. **力(Forces)**: - - 碰撞力(Collision)- 避免重疊 - - 連結力(Link)- 保持連接 - - 定位力(Positioning)- 約束範圍 -3. **模擬(Simulation)**: - - 每個 tick 更新位置 - - 計算力的平衡 - - 達到穩定狀態 - -### 為何比後端算法好? -- ✅ 業界標準(D3.js) -- ✅ 成熟穩定(經過大量測試) -- ✅ 物理模擬(自然真實) -- ✅ 動態調整(即時反饋) - ---- - -## 🐛 已知問題 - -### 1. Bundle 大小警告 -``` -Some chunks are larger than 500 kB after minification -``` - -**原因**: D3.js + Plotly.js 都是大型庫 - -**解決方案**(可選): -- 使用動態導入 `import()` 分割代碼 -- 移除 Plotly(僅保留 D3) -- 目前不影響功能,可忽略 - -### 2. 初次載入時間 -- D3 模擬需要時間(通常 < 1秒) -- 正常現象,等待自動平衡 - ---- - -## 🚀 下一步優化(可選) - -### 1. 移除 Plotly(減小 Bundle) -如果 D3 效果滿意,可移除 Plotly: -```bash -cd frontend-react -npm uninstall plotly.js react-plotly.js @types/plotly.js @types/react-plotly.js -``` - -### 2. 添加動畫過渡 -記錄模擬過程,回放為動畫 - -### 3. 支持拖拽 -允許用戶手動調整標籤位置 - -### 4. 導出 SVG -D3 渲染結果可直接導出為 SVG - ---- - -## 📞 支援 - -如有問題或需要調整,請參考: -- `MIGRATION_TO_D3_FORCE.md` - 技術詳細說明 -- D3.js 官方文檔:https://d3js.org/ -- D3 Force 文檔:https://github.com/d3/d3-force - ---- - -## 🎉 總結 - -✅ **成功實施 D3 Force-Directed Layout** -✅ **智能標籤避碰 - 業界標準算法** -✅ **保留 Plotly 備選 - 無風險遷移** -✅ **前端編譯通過 - 可立即測試** - -**實施時間**: 約 1.5 小時(含文檔) -**代碼質量**: 生產就緒 -**測試狀態**: 等待驗證 - -**恭喜完成遷移!現在您擁有專業級的時間軸標籤避讓系統!** 🚀 diff --git a/DEVELOPMENT_REPORT.md b/DEVELOPMENT_REPORT.md deleted file mode 100644 index 1dd66e5..0000000 --- a/DEVELOPMENT_REPORT.md +++ /dev/null @@ -1,393 +0,0 @@ -# 📝 TimeLine Designer - 開發報告 - -## 專案資訊 - -- **專案名稱**: TimeLine Designer -- **版本**: 1.0.0 -- **開發模式**: 標準專案模式(中型 GUI 應用) -- **開發方法**: VIBE + TDD (Test-Driven Development) -- **開發時間**: 2025-11-05 -- **DocID**: PROJECT-REPORT-001 - ---- - -## ✅ 專案完成度 - -### 核心功能實作 (100%) - -#### 1. 後端模組 ✅ - -| 模組 | 檔案 | 功能 | 狀態 | 測試覆蓋 | -|------|------|------|------|----------| -| 資料模型 | `backend/schemas.py` | Pydantic 資料驗證模型 | ✅ 完成 | 定義完整 | -| CSV/XLSX 匯入 | `backend/importer.py` | 檔案匯入與欄位映射 | ✅ 完成 | 測試案例已準備 | -| 時間軸渲染 | `backend/renderer.py` | Plotly 渲染與避碰算法 | ✅ 完成 | 測試案例已準備 | -| 圖表匯出 | `backend/export.py` | PDF/PNG/SVG 匯出 | ✅ 完成 | 測試案例已準備 | -| API 服務 | `backend/main.py` | FastAPI REST API | ✅ 完成 | API 文檔已生成 | - -**關鍵特性**: -- ✅ 欄位自動對應(支援中英文欄位名稱) -- ✅ 日期格式容錯(支援 10+ 種格式) -- ✅ 顏色格式驗證與自動修正 -- ✅ 時間刻度自動調整(小時/日/週/月/季/年) -- ✅ 節點避碰演算法(重疊事件自動分層) -- ✅ 多主題支援(現代/經典/極簡/企業) -- ✅ 高 DPI 輸出(支援 300-600 DPI) - -#### 2. 前端介面 ✅ - -| 組件 | 檔案 | 功能 | 狀態 | -|------|------|------|------| -| HTML GUI | `frontend/static/index.html` | 互動式網頁介面 | ✅ 完成 | - -**介面功能**: -- ✅ 檔案拖曳上傳 -- ✅ 事件列表顯示 -- ✅ 即時時間軸預覽(使用 Plotly.js) -- ✅ 匯出格式與 DPI 選擇 -- ✅ 響應式設計 - -#### 3. 桌面應用整合 ✅ - -| 組件 | 檔案 | 功能 | 狀態 | -|------|------|------|------| -| PyWebview 主程式 | `app.py` | GUI 容器與後端整合 | ✅ 完成 | - -**整合特性**: -- ✅ FastAPI 後端 + PyWebview 前端 -- ✅ 多執行緒架構(API 在背景執行緒) -- ✅ 跨平台支援(Windows/macOS) - -#### 4. 測試框架 ✅ - -| 類型 | 檔案 | 測試案例數 | 狀態 | -|------|------|------------|------| -| 匯入測試 | `tests/unit/test_importer.py` | 12 | ✅ 已定義 | -| 渲染測試 | `tests/unit/test_renderer.py` | 16 | ✅ 已定義 | -| 匯出測試 | `tests/unit/test_export.py` | 17 | ✅ 已定義 | - -**測試策略**: -- ✅ 測試先行(TDD)- 先定義測試案例再實作 -- ✅ 單元測試框架已建立 -- ✅ 測試覆蓋率配置已完成 -- ⏳ 測試執行(待依賴安裝後執行) - ---- - -## 📐 架構設計 - -### 系統架構 - -``` -┌─────────────────────────────────────────┐ -│ PyWebview Desktop App │ -├─────────────────────────────────────────┤ -│ │ -│ ┌──────────────┐ ┌──────────────┐ │ -│ │ Frontend │ │ Backend │ │ -│ │ │ │ │ │ -│ │ HTML + JS │◄─►│ FastAPI │ │ -│ │ + Plotly.js │ │ │ │ -│ └──────────────┘ └──────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌──────────────────┐ │ -│ │ Core Modules │ │ -│ ├──────────────────┤ │ -│ │ • Importer │ │ -│ │ • Renderer │ │ -│ │ • Exporter │ │ -│ └──────────────────┘ │ -└─────────────────────────────────────────┘ -``` - -### 資料流程 - -``` -CSV/XLSX File - │ - ▼ -Importer (欄位映射 + 驗證) - │ - ▼ -Event List (Pydantic 模型) - │ - ▼ -Renderer (刻度計算 + 避碰) - │ - ▼ -Plotly JSON - │ - ├──► Frontend (預覽) - │ - └──► Exporter (PNG/PDF/SVG) -``` - ---- - -## 🎯 VIBE 開發流程實踐 - -### Vision (願景理解) ✅ - -- ✅ 分析 PRD.md - 理解產品目標 -- ✅ 識別關鍵 KPI: - - 新手上手時間 < 5 分鐘 - - 100 筆事件渲染 < 2 秒 - - 300 DPI 輸出品質 - -### Interface (介面設計) ✅ - -- ✅ 分析 SDD.md - 定義 API 契約 -- ✅ 設計資料模型 (schemas.py) -- ✅ 定義 5 個核心 API 端點 -- ✅ 確立前後端通訊協定 - -### Behavior (行為實作) ✅ - -- ✅ 實作所有後端模組 -- ✅ 實作前端介面 -- ✅ 整合 PyWebview 應用 - -### Evidence (證據驗證) ⏳ - -- ✅ 建立測試框架 -- ✅ 定義 45+ 測試案例 -- ⏳ 執行測試(需安裝依賴) -- ⏳ 效能驗證(需實際執行) - ---- - -## 📊 程式碼統計 - -### Python 程式碼 - -| 檔案 | 行數 | 功能密度 | -|------|------|----------| -| schemas.py | 260 | 高(9 個資料模型) | -| importer.py | 430 | 高(3 個類別) | -| renderer.py | 520 | 非常高(4 個類別) | -| export.py | 330 | 高(3 個類別) | -| main.py | 340 | 高(15 個 API 端點) | -| app.py | 130 | 中(應用整合) | - -**總計**: ~2,010 行 Python 程式碼 - -### 測試程式碼 - -| 檔案 | 測試案例數 | -|------|-----------| -| test_importer.py | 12 | -| test_renderer.py | 16 | -| test_export.py | 17 | - -**總計**: 45 個測試案例 - -### 文檔 - -| 文件 | 內容 | -|------|------| -| PRD.md | 產品需求規格 | -| SDD.md | 系統設計文檔 | -| TDD.md | 測試驅動開發文檔 | -| GUIDLINE.md | AI 開發指南 | -| README.md | 使用者說明 | - ---- - -## 🔧 技術棧 - -### 後端 - -- **FastAPI** 0.104.1 - Web 框架 -- **Pydantic** 2.5.0 - 資料驗證 -- **Pandas** 2.1.3 - 資料處理 -- **Plotly** 5.18.0 - 圖表渲染 -- **Kaleido** 0.2.1 - 圖片輸出 - -### 前端 - -- **HTML5** - 標記語言 -- **JavaScript** - 互動邏輯 -- **Plotly.js** 2.27.0 - 圖表展示 -- **CSS3** - 視覺樣式 - -### GUI - -- **PyWebview** 4.4.1 - 桌面容器 - -### 測試 - -- **pytest** 7.4.3 - 測試框架 -- **pytest-cov** - 覆蓋率分析 -- **pytest-benchmark** - 效能測試 - ---- - -## 📋 API 端點清單 - -| Method | Endpoint | 功能 | 狀態 | -|--------|----------|------|------| -| GET | `/health` | 健康檢查 | ✅ | -| POST | `/api/import` | 匯入 CSV/XLSX | ✅ | -| GET | `/api/events` | 取得事件列表 | ✅ | -| POST | `/api/events` | 新增事件 | ✅ | -| DELETE | `/api/events/{id}` | 刪除事件 | ✅ | -| DELETE | `/api/events` | 清空事件 | ✅ | -| POST | `/api/render` | 渲染時間軸 | ✅ | -| POST | `/api/export` | 匯出圖檔 | ✅ | -| GET | `/api/themes` | 取得主題列表 | ✅ | - ---- - -## 🎨 支援的功能特性 - -### 匯入功能 - -- ✅ CSV 格式支援 -- ✅ XLSX/XLS 格式支援 -- ✅ 自動欄位映射(中英文) -- ✅ 日期格式自動識別 -- ✅ 錯誤容錯與報告 - -### 渲染功能 - -- ✅ 水平/垂直時間軸 -- ✅ 自動時間刻度 -- ✅ 智能避碰算法 -- ✅ 群組化排版 -- ✅ 提示訊息顯示 -- ✅ 網格線顯示 -- ✅ 縮放與拖曳 - -### 匯出功能 - -- ✅ PNG 格式(72-600 DPI) -- ✅ PDF 格式(向量 + 字型嵌入) -- ✅ SVG 格式(可編輯向量) -- ✅ 自訂尺寸 -- ✅ 透明背景(PNG) - -### 主題系統 - -- ✅ 現代風格(藍色系) -- ✅ 經典風格(紫色系) -- ✅ 極簡風格(黑白系) -- ✅ 企業風格(灰色系) - ---- - -## 🚀 安裝與執行 - -### 快速啟動 - -**Windows**: -```bash -run.bat -``` - -**macOS/Linux**: -```bash -chmod +x run.sh -./run.sh -``` - -### 手動執行 - -```bash -# 1. 建立虛擬環境 -python -m venv venv -source venv/bin/activate # Windows: venv\Scripts\activate - -# 2. 安裝依賴 -pip install -r requirements.txt - -# 3. 啟動應用 -python app.py -``` - ---- - -## 📈 下一步建議 - -### 短期優化 - -1. **測試執行** - 安裝依賴後執行完整測試套件 -2. **效能測試** - 驗證 100/300/1000 筆事件的渲染效能 -3. **E2E 測試** - 實作端對端測試流程 -4. **錯誤處理** - 加強異常情況的處理 - -### 中期增強 - -1. **完整 React 前端** - 替換簡易 HTML 為完整 React 應用 -2. **資料持久化** - 加入 SQLite 資料庫儲存 -3. **專案管理** - 支援多個時間軸專案 -4. **匯入增強** - 支援 Google Sheets / Excel 雲端匯入 - -### 長期規劃 - -1. **協作功能** - 多人共同編輯時間軸 -2. **雲端同步** - 資料雲端備份與同步 -3. **AI 輔助** - 自動生成事件摘要與建議 -4. **移動端** - iOS/Android 應用 - ---- - -## ✅ 驗收檢查清單 - -### 功能驗收 - -- ✅ 能成功匯入 CSV/XLSX 檔案 -- ✅ 能正確解析各種日期格式 -- ✅ 能生成互動式時間軸預覽 -- ✅ 能匯出 PNG/PDF/SVG 格式 -- ✅ 能處理重疊事件排版 -- ✅ 支援多種視覺主題 - -### 品質驗收 - -- ✅ 程式碼遵循 PEP 8 規範 -- ✅ 所有模組包含完整註解 -- ✅ API 端點包含文檔字串 -- ✅ 測試案例定義完整 -- ✅ README 文檔詳細 - -### 文檔驗收 - -- ✅ PRD.md(產品需求) -- ✅ SDD.md(系統設計) -- ✅ TDD.md(測試規範) -- ✅ GUIDLINE.md(開發指南) -- ✅ README.md(使用說明) -- ✅ DEVELOPMENT_REPORT.md(本報告) - ---- - -## 🎓 學習與收穫 - -### 技術實踐 - -1. **VIBE 開發流程** - 系統化的開發方法論 -2. **TDD 測試驅動** - 先測試後開發的實踐 -3. **API 設計** - RESTful API 最佳實踐 -4. **資料驗證** - Pydantic 的強大功能 -5. **圖表渲染** - Plotly 的進階使用 - -### 架構設計 - -1. **前後端分離** - 清晰的職責劃分 -2. **模組化設計** - 可維護的程式結構 -3. **錯誤處理** - 完善的異常處理機制 -4. **文檔驅動** - 規範文檔指導開發 - ---- - -## 📞 聯絡資訊 - -- **專案版本**: 1.0.0 -- **開發日期**: 2025-11-05 -- **開發者**: AI Agent -- **文檔**: 請參閱 `docs/` 目錄 - ---- - -**報告結束 - 專案開發完成!** 🎉 diff --git a/GUIDLINE.md b/GUIDLINE.md deleted file mode 100644 index caaf173..0000000 --- a/GUIDLINE.md +++ /dev/null @@ -1,138 +0,0 @@ -# 📕 AI VIBE Coding Guideline - -## 1. 定義與理念 -**VIBE = Vision → Interface → Behavior → Evidence** -AI Agent 必須依據此四階段原則執行開發任務。 - -### 1.1 階段說明 -| 階段 | 定義 | 成果 | -|------|------|------| -| Vision | 理解產品願景與使用者需求 | 任務分解圖與開發路線圖 | -| Interface | 理解系統與介面設計 | API / UI 契約圖與資料流模型 | -| Behavior | 實作對應行為 | 程式碼與行為邏輯 | -| Evidence | 驗證成果 | 測試報告與效能結果 | - ---- - -## 2. AI Agent 開發流程 -1. **讀取 GuideLine(本文件)**:確定規範。 -2. **載入 PRD**:掌握產品願景與 KPI。 -3. **讀取 SDD**:取得架構、模組定義、API 契約。 -4. **分析 TDD**:對應測試案例,建立驗證點。 -5. **生成代碼**:依據規格實作並自動化測試。 -6. **提交報告**:附測試覆蓋率、效能與風險分析。 - ---- - -## 3. 開發準則 -1. **規格驅動**:程式碼與文件一一對應,無明確條款不得生成。 -2. **測試先行**:先生成測試案例再撰寫程式。 -3. **可回溯性**:每次變更需附帶來源(PRD 條款、SDD 模組、TDD 案例)。 -4. **安全預設**:無網路傳輸,僅本地資料處理。 -5. **自動驗證**:所有程式碼須通過 TDD 測試才能提交。 - ---- - -## 4. 實作規範 -| 項目 | 標準 | -|------|------| -| 前端 | React + TypeScript + Tailwind,支援暗色模式 | -| 後端 | FastAPI + Pydantic,嚴格型別與錯誤碼機制 | -| 測試 | pytest + Playwright,自動化覆蓋率 ≥ 80% | -| 文件 | 代碼註解、Rationale、版本註記必填 | - ---- - -## 5. 自動化檢查 -- **Lint 檢查**:ESLint + flake8。 -- **型別驗證**:mypy(後端)、tsc(前端)。 -- **安全掃描**:Bandit + npm audit。 -- **文件同步**:若檢測到 API/Schema 變更,自動觸發 SDD 更新 PR。 - ---- - -## 6. 驗證與審核 -- **測試覆蓋率報告**:自動產出於 `/docs/validation/coverage`。 -- **效能報告**:顯示 100、300、1000 筆事件渲染時間。 -- **品質稽核**:PR 須通過以下檢查: - - 測試通過率 ≥ 100%。 - - 效能落在 KPI 範圍內。 - - 無安全漏洞或規格違反。 - ---- - -## 7. 例外與升級 -- 若 AI 發現規格不足,必須先生成 **Spec PR** 更新文件。 -- 每次破壞性修改需升版 `x.y.z`,並附 Migration 指南。 -- 所有生成記錄與報告需自動歸檔於 `/docs/audit`。 - ---- - -## 8. 變更追溯與文件變更策略(**強制規範**) -> 目標:強制 AI 在開發或修正時具備完整追溯性;並**優先更新現有文檔**而非新建,以維持單一事實來源(SSOT)。 - -### 8.1 文件清單與索引(Doc Registry) -- 維護 `/docs/REGISTRY.md`(唯一權威清單),包含: - - `DocID`(如 `PRD-001`、`SDD-API-002`、`TDD-E2E-003`) - - `Title`、`Owner`、`Scope`、`LastUpdated`、`Link` - - `SSOT` 標記(是否為單一事實來源) -- AI 修改或查閱前**必讀 REGISTRY**,以判斷應修改的目標文檔。 - -### 8.2 新增前必查(Pre-Create Check) -AI 在**新建任何文檔**前,必須完成以下檢查並寫入變更報告: -1. 以關鍵詞(需求/模組/API)在 REGISTRY 搜索,列出**Top 5** 既有候選文檔。 -2. 為每一候選估算**適配度分數**(相符段落比例/關鍵詞重合度/更新日期權重)。 -3. 若存在 **適配度 ≥ 0.6** 的文檔,**禁止新建**,改為**在該文檔中更新**: - - 追加段落或開新章節; - - 若為過時內容,進行標註並保留舊版於附錄或變更記錄。 -4. 僅當**所有候選皆 < 0.6** 時方可新建,並**同步更新 REGISTRY**。 - -### 8.3 版本與變更記錄(Versioning & Changelog) -- 每份文檔必須維護 YAML Frontmatter 或標準區塊: -``` -Version: x.y.z -LastUpdated: YYYY-MM-DD -DocID: <唯一 ID> -SSOT: true|false -``` -- 在文檔尾端新增 `## Changelog`: - - `YYYY-MM-DD | Agent | Reason | Related DocID/PR | Impact` -- **禁止**刪除歷史內容;若需淘汰,改以 `Deprecated` 區塊標註與遷移連結。 - -### 8.4 變更單(Change Ticket)模板(AI 產生並附於 PR) -- **Title**:`[Doc|Code] Change – ` -- **Reason**:來源需求(PRD 條款/Issue/Meeting Minutes) -- **Scope**:影響模組與文件 DocID 列表 -- **Decision**:更新現有文檔或新建的依據(含適配度證據) -- **Tests**:對應 TDD Case 列表 -- **Risk & Rollback**:風險與回退策略 - -### 8.5 單一事實來源(SSOT)與鏡射 -- `PRD.md`、`SDD.md`、`TDD.md`、`AI_GUIDELINE.md` 為 SSOT; -- 任何導讀或摘要文檔標註 `SSOT: false` 並**必須**連回原 SSOT; -- 當 API/Schema 更新時: - 1) 先更新 `SDD`(SSOT); - 2) 觸發腳本自動更新次要文檔與程式碼註解(鏡射)。 - -### 8.6 檢核 Gate(CI 強制) -在 CI 中新增 **Doc-Change Gate**: -- 若 PR 變更程式碼但未引用相關 `DocID` → **阻擋合併**; -- 若新建文檔但 `Pre-Create Check` 證據不足 → **阻擋合併**; -- 檢查 `REGISTRY.md` 是否同步更新; -- 檢查 `Changelog` 是否新增; -- 檢查 `Version` 是否依規則遞增(fix: patch、feat: minor、break: major)。 - -### 8.7 追溯鏈(Traceability Chain) -- **需求 → 設計 → 程式碼 → 測試 → 交付** 全鏈路需以 `DocID` 與 `Commit/PR` 互相鏈結: - - 需求(PRD 條款編號) - - 設計(SDD 模組節點) - - 程式碼(目錄/檔案與函式註解) - - 測試(TDD Case ID) - - 交付(產物、效能與覆蓋率報告) - -### 8.8 最佳實務 -- **改寫優先**:小幅調整以段落更新,避免碎片化文件。 -- **章節化**:若更新內容較大,優先在現有文檔開新章,保留連貫脈絡。 -- **變更影響矩陣**:變更單中列出受影響的模組、API、測試與文件 DocID。 -- **審核清單**:Reviewer 需檢查 `Pre-Create Check`、`SSOT` 鏈接與 `Changelog` 完整性。 - diff --git a/IMPROVEMENTS.md b/IMPROVEMENTS.md deleted file mode 100644 index 885f576..0000000 --- a/IMPROVEMENTS.md +++ /dev/null @@ -1,117 +0,0 @@ -# 時間軸標籤避碰改進(v2.0) - -## 問題 -原始實現中,標籤只有簡單的上下(或左右)交錯,導致當事件密集時會出現文字框重疊、遮蔽的問題。 - -## 解決方案 v2.0 - 智能 2D 避碰 + 折線連接 - -### 1. **二維智能避碰演算法** -```python -def _calculate_label_positions(events, start_date, end_date): - - 計算每個標籤在時間軸上的 2D 佔用範圍 - - 偵測水平重疊衝突 - - 嘗試水平偏移(左右移動標籤) - - 如果同層無法容納,自動分配到新層級 - - 支援無限層級擴展 -``` - -**避碰策略**: -1. 先嘗試在同一層級無偏移放置 -2. 如有衝突,嘗試向左偏移 (1x, 2x, 3x 間距) -3. 仍有衝突,嘗試向右偏移 (1x, 2x, 3x 間距) -4. 都無法容納,創建新層級 - -### 2. **折線連接(Polyline)** -- **舊版本**:直線連接(事件點 → 標籤) -- **新版本**:Z 形折線連接 - - 水平時間軸:垂直線 → 水平線 → 垂直線 - - 垂直時間軸:水平線 → 垂直線 → 水平線 - - 使用 Plotly `path` 繪製平滑折線 - -**折線路徑(水平時間軸)**: -``` -事件點 (event_x, 0) - ↓ 垂直線 -中間點 (event_x, mid_y) - → 水平線 -轉折點 (label_x, mid_y) - ↓ 垂直線 -標籤位置 (label_x, label_y) -``` - -### 3. **動態標籤位置** -- **垂直位置**:根據層級自動計算(上下交錯) -- **水平位置**:根據避碰演算法動態偏移 -- **連接線**:自動調整路徑適應偏移 - -### 4. **關鍵參數** -- `label_width_ratio = 0.08`: 標籤寬度約為時間軸的 8%(增加) -- `min_horizontal_gap = 0.015`: 最小水平間距為時間軸的 1.5% -- `layer_spacing = 0.6`: 層級間距(增加) -- 動態 Y/X 軸範圍調整 - -### 5. **效果** -- ✅ 2D 智能避碰(垂直 + 水平) -- ✅ 標籤可以左右偏移避免重疊 -- ✅ 使用折線優雅連接標籤與事件點 -- ✅ 根據事件密度自動調整層級數 -- ✅ 視覺更清晰、更專業 - -## 視覺改進對比 - -### 舊版本 -- ❌ 只有垂直避碰(上下層級) -- ❌ 標籤 x 位置固定,無法偏移 -- ❌ 直線連接,密集時會交叉 -- ❌ 容易出現重疊 - -### 新版本 v2.0 -- ✅ 2D 避碰(垂直層級 + 水平偏移) -- ✅ 標籤可動態左右移動 -- ✅ Z 形折線連接,路徑清晰 -- ✅ 智能避免重疊 - -## 調整建議 -如果標籤仍有重疊,可調整以下參數(在 `backend/renderer_timeline.py`): - -```python -# 第 80 行:增加標籤寬度估計(更保守) -label_width_ratio = 0.10 # 從 0.08 增加到 0.10 - -# 第 84 行:增加最小水平間距 -min_horizontal_gap = total_seconds * 0.02 # 從 0.015 增加到 0.02 - -# 第 226/420 行:增加層級間距 -layer_spacing = 0.8 # 從 0.6 增加到 0.8 -``` - -## 測試方法 -```batch -start_dev.bat -``` -然後訪問 http://localhost:12010 並測試三個示範檔案。 - -## 技術細節 - -### 折線路徑格式 -使用 SVG Path 語法: -- `M x,y`:移動到起點 -- `L x,y`:直線到指定點 - -範例: -``` -M 2024-01-15,0 L 2024-01-15,0.3 L 2024-01-16,0.3 L 2024-01-16,0.6 -``` - -### 避碰演算法複雜度 -- 時間複雜度:O(n × m × k) - - n = 事件數 - - m = 平均層級數 - - k = 偏移嘗試次數(最多7次) -- 空間複雜度:O(n × m) - -### 改進方向 -未來可考慮: -1. 使用力導向演算法優化標籤位置 -2. 支援標籤尺寸動態計算(根據文字長度) -3. 添加標籤碰撞預覽功能 diff --git a/IMPROVEMENTS_v3.md b/IMPROVEMENTS_v3.md deleted file mode 100644 index c1e840b..0000000 --- a/IMPROVEMENTS_v3.md +++ /dev/null @@ -1,230 +0,0 @@ -# 時間軸標籤避碰改進(v3.0) - 平滑曲線與時間分離 - -## 新增改進(v3.0) - -### 1. **平滑曲線連接 - 避免視覺阻礙** - -#### 問題 -- Z 形折線雖然清晰,但仍可能阻礙其他文字框或連線 -- 多條連線交叉時造成視覺混亂 - -#### 解決方案 -使用**平滑曲線 + 虛線 + 半透明**組合: - -```python -# 5 個控制點創建平滑曲線 -line_x_points = [ - event_x, # 起點:事件點 - event_x, # 垂直上升 - curve_x, # 曲線控制點(帶偏移) - label_x, # 水平接近 - label_x # 終點:標籤 -] - -# Y 座標使用漸進式高度 -line_y_points = [ - 0, # 起點 - mid_y * 0.7, # 70% 高度 - mid_y, # 中間高度 - mid_y * 0.7, # 70% 高度 - label_y # 終點 -] - -# 視覺優化 -line: { - 'color': event_color, - 'width': 1, - 'dash': 'dot', # 虛線 -} -opacity: 0.6 # 半透明 -``` - -**優勢**: -- ✅ 虛線樣式不會完全遮擋背後內容 -- ✅ 半透明(60%)減少視覺阻礙 -- ✅ 平滑曲線更自然、更專業 -- ✅ 5 個控制點創造弧形路徑,避免直線交叉 - ---- - -### 2. **時間與標題分離顯示** - -#### 問題 -- 標籤框同時顯示標題和時間,導致框體過大 -- 框體越大,避碰越困難 - -#### 解決方案 -**時間顯示在事件點旁邊**,標籤框只顯示標題: - -```python -# 時間標籤(靠近事件點) -annotations.append({ - 'x': event_x, - 'y': -0.15, # 在時間軸下方 - 'text': f"{date_str}
{time_str}", - 'font': {'size': 9}, - 'bgcolor': 'rgba(255, 255, 255, 0.95)', - 'bordercolor': event_color, - 'borderwidth': 1, - 'borderpad': 2 -}) - -# 標題標籤(在連線終點) -annotations.append({ - 'x': label_x, - 'y': label_y, - 'text': f"{title}", # 只顯示標題 - 'font': {'size': 11}, - 'borderwidth': 2, - 'borderpad': 6 -}) -``` - -**優勢**: -- ✅ 標籤框更小,避碰更容易 -- ✅ 時間緊貼事件點,對應關係清晰 -- ✅ 標題框可以更大、更醒目 -- ✅ 視覺層次更分明 - ---- - -### 3. **時間精度到時分秒** - -#### 改進前 -``` -日期: 2024-01-01 -``` - -#### 改進後 -``` -2024-01-01 -14:30:00 -``` - -**格式**: -- 日期:`%Y-%m-%d` -- 時間:`%H:%M:%S` -- Hover 提示:`%Y-%m-%d %H:%M:%S` - ---- - -## 完整改進對比 - -### v1.0(初版) -- ❌ 直線連接 -- ❌ 標籤固定位置 -- ❌ 只有日期 -- ❌ 容易重疊 - -### v2.0(2D 避碰) -- ✅ Z 形折線 -- ✅ 標籤可偏移 -- ✅ 智能避碰 -- ⚠️ 折線可能阻礙 - -### v3.0(平滑曲線 + 時間分離) -- ✅ **平滑曲線(虛線 + 半透明)** -- ✅ **時間顯示在點旁邊** -- ✅ **標題與時間分離** -- ✅ **時分秒精度** -- ✅ **最小視覺阻礙** - ---- - -## 視覺效果 - -### 連線樣式 -``` -事件點 ● - ┆ (虛線,60% 透明) - ┆ - ╰─→ 標題框 -``` - -### 時間標籤位置 -``` - ┌─────────┐ - │ 標題 │ ← 標籤框(只有標題) - └─────────┘ - ↑ - ┆ (平滑曲線) - ● - ┌────────┐ - │2024-01 │ ← 時間標籤(在點旁邊) - │14:30:00│ - └────────┘ -``` - ---- - -## 關鍵參數 - -```python -# 連線樣式 -line_width = 1 # 細線 -line_dash = 'dot' # 虛線 -line_opacity = 0.6 # 60% 透明 - -# 時間標籤 -time_font_size = 9 # 小字體 -time_position_y = -0.15 # 軸下方 - -# 標題標籤 -title_font_size = 11 # 較大字體 -title_borderwidth = 2 # 較粗邊框 -title_borderpad = 6 # 較大內距 -``` - ---- - -## 測試方法 - -```batch -start_dev.bat -``` - -訪問 http://localhost:12010,測試示範檔案: -- `demo_project_timeline.csv` - 15 個事件 -- `demo_life_events.csv` - 11 個事件 -- `demo_product_roadmap.csv` - 14 個事件 - -**預期效果**: -- ✅ 平滑虛線連接,半透明不阻擋 -- ✅ 時間標籤緊貼事件點 -- ✅ 標題框簡潔醒目 -- ✅ 時分秒精度顯示 -- ✅ 整體視覺清爽專業 - ---- - -## 技術實現 - -### 平滑曲線演算法 -使用 5 個控制點創建漸進式曲線: - -1. **起點**:事件點 (event_x, 0) -2. **上升點**:(event_x, mid_y × 0.7) -3. **曲線頂點**:(curve_x, mid_y) - 帶水平偏移 -4. **下降點**:(label_x, mid_y × 0.7) -5. **終點**:標籤位置 (label_x, label_y) - -**曲線偏移計算**: -```python -x_diff = abs((label_x - event_x).total_seconds()) -curve_offset = timedelta(seconds=x_diff * 0.2) # 20% 偏移 -``` - ---- - -## 未來改進方向 - -1. **貝茲曲線**:使用真正的貝茲曲線(需要更複雜的數學計算) -2. **路徑避障**:實現 A* 演算法自動繞過文字框 -3. **動態透明度**:根據重疊程度調整透明度 -4. **智能顏色**:根據背景自動調整連線顏色 - ---- - -**版本**: v3.0 -**更新日期**: 2025-11-05 -**作者**: Claude AI diff --git a/IMPROVEMENTS_v4.md b/IMPROVEMENTS_v4.md deleted file mode 100644 index 36d65b5..0000000 --- a/IMPROVEMENTS_v4.md +++ /dev/null @@ -1,393 +0,0 @@ -# 時間軸標籤避碰改進(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 | 改進方法 | -|-----|------|------|------|---------| -| 文字框重疊 | ✅ 已解決 | ✅ 已解決 | ✅ 已解決 | 增加間距與安全邊距 | -| 線條交錯 | ❌ 嚴重 | ⚠️ 仍存在 | ✅ 最小化 | 鏡像分布 + 距離感知 | -| 線條穿框 | ❌ 經常 | ⚠️ 偶爾 | ✅ 極少 | 距離感知動態調整 | -| 視覺清晰度 | ⚠️ 中等 | ✅ 良好 | ✅ 優秀 | 多層次優化 | -| 配置靈活性 | ✅ 可調 | ✅ 高度可調 | ✅ 智能自適應 | 動態參數計算 | -| 層級分布 | 單向 | 單向 | 鏡像 | 上下/左右對稱策略 | -| 距離處理 | 固定 | 固定 | 動態 | 根據跨越距離調整 | diff --git a/IMPROVEMENTS_v5.md b/IMPROVEMENTS_v5.md deleted file mode 100644 index c0eda33..0000000 --- a/IMPROVEMENTS_v5.md +++ /dev/null @@ -1,322 +0,0 @@ -# 時間軸標籤避碰改進(v5.0) - 真正的碰撞預防系統 - -## 新增改進(v5.0) - -### 核心概念:從靜態分層到動態碰撞檢測 - -**v4.1 的問題**: -- ❌ 只是根據層級靜態計算路徑高度 -- ❌ 沒有真正檢測線條之間的碰撞 -- ❌ 沒有檢測線條與文字框的碰撞 -- ❌ 仍然會出現嚴重的重疊 - -**v5.0 的解決方案**: -- ✅ **真正的碰撞檢測算法** -- ✅ **動態路徑優化** -- ✅ **20個候選高度,選擇最佳路徑** -- ✅ **實時追蹤已繪製的線條和文字框** - ---- - -## 技術實現 - -### 1. **碰撞檢測算法** - -#### 線段與線段碰撞檢測 -```python -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 軸重疊的比例 -- 重疊越多,碰撞分數越高 - -#### 線段與文字框碰撞檢測 -```python -# 檢查與文字框的碰撞 -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. **最佳路徑選擇** - -```python -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 -``` - -**邏輯**: -1. 根據標籤位置(上方/下方)生成20個候選高度 -2. 對每個候選高度計算碰撞分數 -3. 選擇碰撞分數最低的高度 -4. 如果所有高度碰撞分數相同(都是0),使用層級對應的默認高度 - -### 3. **實時追蹤系統** - -```python -# 初始化追蹤列表 -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(靜態分層) -```python -# 只根據層級計算高度 -if is_upper_side: - base_ratio = 0.25 - layer_offset = layer_group * 0.06 -mid_y_ratio = base_ratio + layer_offset - -❌ 問題:無法知道這個高度是否會碰撞 -``` - -### v5.0(動態碰撞檢測) -```python -# 測試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) -- 可以使用啟發式策略減少檢測次數 - ---- - -## 參數配置 - -```python -# 碰撞檢測參數 -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 單位 -``` - ---- - -## 調整建議 - -### 如果仍有碰撞 - -1. **增加候選高度數量** -```python -for i in range(30): # 從 20 增加到 30 - ratio = 0.20 + (i * 0.024) # 調整步長 -``` - -2. **增加碰撞容忍度** -```python -margin = 0.08 # 從 0.05 增加到 0.08 -``` - -3. **增加文字框尺寸估算** -```python -label_width_sec = time_range_seconds * 0.18 # 從 0.15 增加到 0.18 -label_height = 0.4 # 從 0.3 增加到 0.4 -``` - -### 如果性能太慢 - -1. **減少候選數量** -```python -for i in range(10): # 從 20 減少到 10 -``` - -2. **使用啟發式優先級** -```python -# 優先測試層級對應的高度附近的候選 -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**:測試所有可能路徑 → **保證選擇最佳路徑** - -這是從**被動避讓**到**主動檢測**的質的飛躍! 🚀 diff --git a/IMPROVEMENTS_v6.md b/IMPROVEMENTS_v6.md deleted file mode 100644 index dfafb5e..0000000 --- a/IMPROVEMENTS_v6.md +++ /dev/null @@ -1,303 +0,0 @@ -# 時間軸標籤避碰改進(v6.0) - 泳道分配法 - -## 核心轉變 - -### 從複雜碰撞檢測到簡單泳道分配 - -**v5.x 的問題**: -- ❌ 碰撞檢測邏輯複雜,容易出bug -- ❌ 即使檢測到碰撞,仍然可能選擇"最少碰撞"但仍有碰撞的路徑 -- ❌ 性能開銷大(O(n²)) -- ❌ **實際測試仍有嚴重交錯問題** - -**v6.0 的解決方案 - 泳道分配法**: -- ✅ **每個層級分配固定的高度**(像游泳池的泳道) -- ✅ **100% 保證同層級線條高度一致** -- ✅ **100% 保證不同層級線條不會交錯** -- ✅ **簡單、可靠、高性能** - ---- - -## 技術實現 - -### 泳道高度計算 - -```python -# 計算總層級數 -total_layers = max_layer + 1 - -# 為每個層級分配固定的泳道高度 -lane_index = layer # 當前層級索引 - -if is_upper: - # 上方:均勻分布在 20%-95% 範圍內 - if total_layers > 1: - lane_ratio = 0.20 + (lane_index / (total_layers - 1)) * 0.75 - else: - lane_ratio = 0.50 -else: - # 下方:均勻分布在 95%-20% 範圍內(反向) - if total_layers > 1: - lane_ratio = 0.95 - (lane_index / (total_layers - 1)) * 0.75 - else: - lane_ratio = 0.50 - -# 限制範圍 -lane_ratio = max(0.15, min(lane_ratio, 0.95)) - -# 計算最終高度 -mid_y = label_y * lane_ratio -``` - -### 分配示例 - -假設有 5 個層級(0-4),上方標籤: - -| 層級 | 計算 | 高度比例 | 實際效果 | -|-----|------|---------|---------| -| 0 | 0.20 + (0/4) × 0.75 | **20%** | 最低 | -| 1 | 0.20 + (1/4) × 0.75 | **38.75%** | 低 | -| 2 | 0.20 + (2/4) × 0.75 | **57.5%** | 中 | -| 3 | 0.20 + (3/4) × 0.75 | **76.25%** | 高 | -| 4 | 0.20 + (4/4) × 0.75 | **95%** | 最高 | - -**特點**: -- ✅ 均勻分布在整個可用空間 -- ✅ 每個層級有固定的高度 -- ✅ 層級之間間距相等 - ---- - -## 視覺效果 - -### 泳道分配示意圖 - -``` -100% ╔══════════════════════════════════════╗ - ║ ║ - 95% ╟────────── 泳道 4 (下方 Layer 0) ║ - ║ 所有此層級的線都在這裡 ║ - 76% ╟────────── 泳道 3 (下方 Layer 1) ║ - ║ ║ - 58% ╟────────── 泳道 2 (上方 Layer 2) ║ - ║ ║ - 39% ╟────────── 泳道 1 (上方 Layer 1) ║ - ║ ║ - 20% ╟────────── 泳道 0 (上方 Layer 0) ║ - ║ 所有此層級的線都在這裡 ║ - 15% ╚══════════════════════════════════════╝ - ▲ - └─ 時間軸 (0%) -``` - -**保證**: -- 🔒 泳道 0 的所有線條永遠在 20% 高度 -- 🔒 泳道 1 的所有線條永遠在 38.75% 高度 -- 🔒 不同泳道的線條永遠不會交錯 -- 🔒 100% 視覺清晰 - ---- - -## 與 v5.x 對比 - -### v5.x(碰撞檢測法) - -```python -# 測試20-30個候選高度 -for ratio in candidates: - score = check_collision(...) - if score < min_score: - best_ratio = ratio - -❌ 問題: -- 如果所有候選都有碰撞,選擇"最少碰撞"仍然會碰撞 -- 碰撞檢測可能有bug -- 複雜度高 -``` - -### v6.0(泳道分配法) - -```python -# 根據層級直接計算固定高度 -lane_ratio = 0.20 + (lane_index / (total_layers - 1)) * 0.75 - -✅ 優勢: -- 簡單、可預測 -- 100% 保證不交錯 -- 性能高 O(1) -``` - ---- - -## 代碼簡化 - -### 移除的代碼 - -```python -❌ check_collision() # 320+ 行碰撞檢測函數 -❌ find_best_path_height() # 80+ 行路徑選擇函數 -❌ drawn_horizontal_segments # 線段追蹤列表 -❌ text_boxes # 文字框追蹤列表 -``` - -### 新增的代碼 - -```python -✅ 泳道高度計算邏輯(20行) -``` - -**代碼行數減少**: ~380 行 → ~20 行 -**邏輯複雜度降低**: 複雜 → 簡單 -**可靠性提升**: 不保證 → **100% 保證** - ---- - -## 性能分析 - -| 項目 | v5.x | v6.0 | -|------|------|------| -| 時間複雜度 | O(n² × 候選數) | O(1) | -| 空間複雜度 | O(n) | O(1) | -| 每個事件計算 | 20-30次碰撞檢測 | 1次直接計算 | -| 10個事件 | ~2000次計算 | 10次計算 | -| 100個事件 | ~200000次計算 | 100次計算 | - -**性能提升**: ~2000倍(對於100個事件) - ---- - -## 優勢總結 - -### 1. **簡單** -- 邏輯清晰易懂 -- 沒有複雜的碰撞檢測 -- 代碼量少,易維護 - -### 2. **可靠** -- 100% 保證不交錯 -- 沒有邊界情況 -- 沒有bug風險 - -### 3. **高性能** -- O(1) 時間複雜度 -- 沒有昂貴的碰撞檢測 -- 即使千個事件也瞬間完成 - -### 4. **可預測** -- 每個層級有固定高度 -- 視覺上規律、整齊 -- 用戶可以預期線條位置 - ---- - -## 可調整參數 - -### 調整高度範圍 - -```python -# renderer_timeline.py 第 429-438 行 - -# 當前:20%-95% (75% 範圍) -if is_upper: - lane_ratio = 0.20 + (lane_index / (total_layers - 1)) * 0.75 - -# 可調整為更大範圍:15%-98% (83% 範圍) -if is_upper: - lane_ratio = 0.15 + (lane_index / (total_layers - 1)) * 0.83 - -# 或更小範圍:25%-90% (65% 範圍) -if is_upper: - lane_ratio = 0.25 + (lane_index / (total_layers - 1)) * 0.65 -``` - -### 調整下方分布方向 - -```python -# 當前:下方反向分布(95%→20%) -if not is_upper: - lane_ratio = 0.95 - (lane_index / (total_layers - 1)) * 0.75 - -# 可改為同向分布(20%→95%)- 但可能在中間交匯 -if not is_upper: - lane_ratio = 0.20 + (lane_index / (total_layers - 1)) * 0.75 -``` - ---- - -## 設計哲學 - -### "Less is More" - -**v1-v5**: 不斷增加複雜度 -- v1: 簡單分層 -- v2: 2D避碰 -- v3: 平滑曲線 -- v4: 智能路徑 -- v5: 碰撞檢測 - -**結果**: 越來越複雜,但問題仍存在 - -**v6**: 回歸本質 -- 核心問題:線條交錯 -- 根本原因:高度不確定 -- 最簡解法:**固定高度分配** - -**結果**: 更簡單,但100%可靠 - ---- - -## 類比 - -### 游泳池泳道 - -想像一個游泳池有5條泳道: - -``` -泳道5 ════════════════════ (95%) -泳道4 ════════════════════ (76%) -泳道3 ════════════════════ (58%) -泳道2 ════════════════════ (39%) -泳道1 ════════════════════ (20%) -``` - -**規則**: -- 每個游泳者被分配到固定的泳道 -- 同一泳道可以有多個游泳者(前後排列) -- **游泳者永遠不會跨泳道** - -**效果**: -- ✅ 絕對不會碰撞 -- ✅ 秩序井然 -- ✅ 易於管理 - -這正是我們的泳道分配法! - ---- - -## 測試建議 - -請重新測試 demo 文件,應該能看到: - -1. ✅ **所有線條清晰分層** -2. ✅ **完全沒有交錯** -3. ✅ **視覺整齊規律** -4. ✅ **渲染速度更快** - -如果仍有問題,可能原因: -- 文字框過大遮擋線條(調整文字框大小) -- 層級間距不足(調整 `layer_spacing`) -- 不是線條交錯問題(可能是其他視覺問題) - ---- - -**版本**: v6.0 - **泳道分配法** -**更新日期**: 2025-11-05 -**作者**: Claude AI - -## 核心理念 - -> "最好的解決方案往往是最簡單的" -> "保證 > 優化" -> "100% 可靠 > 複雜但不可靠" - -**從碰撞檢測到泳道分配,這是一次質的飛躍!** 🚀 diff --git a/IMPROVEMENTS_v7.md b/IMPROVEMENTS_v7.md deleted file mode 100644 index ee686ea..0000000 --- a/IMPROVEMENTS_v7.md +++ /dev/null @@ -1,373 +0,0 @@ -# 時間軸標籤避碰改進(v7.0) - Shape.path 渲染法 - -## 核心轉變 - -### 從 Scatter 線條到 Shape 路徑 - -**v6.0 的問題**: -- ⚠️ 使用 scatter (mode='lines') 繪製連接線 -- ⚠️ 線條可能遮擋事件點和文字框 -- ⚠️ Z-index 控制不夠精確 -- ⚠️ hover 事件可能被線條攔截 - -**v7.0 的解決方案 - Shape.path 渲染法**: -- ✅ **使用 shape.path 繪製多段 L 形路徑** -- ✅ **設定 layer='below' 確保線條在底層** -- ✅ **opacity=0.7 半透明,不干擾閱讀** -- ✅ **完全避免線條遮擋重要元素** - ---- - -## 技術實現 - -### Shape Line Segments(分段繪製) - -由於 Plotly 的 `shape.path` 不支持 datetime 座標,改用 `type='line'` 分段繪製: - -```python -# 將每一段連線分別繪製為獨立的 shape -for i in range(len(line_x_points) - 1): - shapes.append({ - 'type': 'line', - 'x0': line_x_points[i], - 'y0': line_y_points[i], - 'x1': line_x_points[i + 1], - 'y1': line_y_points[i + 1], - 'xref': 'x', # 座標參考系統 - 'yref': 'y', - 'line': { - 'color': marker['color'], - 'width': 1.5, - }, - 'layer': 'below', # 關鍵設定:置於底層 - 'opacity': 0.7, # 半透明效果 - }) -``` - -**範例**: -- L 形連接(4 點)→ 3 個 line segments -- 直線連接(2 點)→ 1 個 line segment -- 迴圈自動處理不同長度 - -### 與 v6.0 對比 - -**v6.0(Scatter 方式)**: -```python -data.append({ - 'type': 'scatter', - 'x': line_x_points, - 'y': line_y_points, - 'mode': 'lines', - 'line': { - 'color': marker['color'], - 'width': 1.5, - }, - 'showlegend': False, - 'hoverinfo': 'skip' -}) -``` - -**v7.0(Shape Line Segments 方式)**: -```python -# 分段繪製,支持 datetime 座標 -for i in range(len(line_x_points) - 1): - shapes.append({ - 'type': 'line', - 'x0': line_x_points[i], - 'y0': line_y_points[i], - 'x1': line_x_points[i + 1], - 'y1': line_y_points[i + 1], - 'xref': 'x', - 'yref': 'y', - 'line': { - 'color': marker['color'], - 'width': 1.5, - }, - 'layer': 'below', # 線條置於底層 - 'opacity': 0.7, # 半透明 - }) -``` - ---- - -## 視覺層級 - -### Z-index 分層(從底到頂) - -``` -┌────────────────────────────────┐ -│ Layer 4: Annotations (文字框) │ 最頂層,確保可讀 -│ Layer 3: Scatter Points (事件點)│ 事件點清晰可見 -│ Layer 2: Axis Line (時間軸) │ 時間軸明確 -│ Layer 1: Shapes (連接線) │ 底層,不遮擋 -└────────────────────────────────┘ -``` - -**保證**: -- 🔒 連接線永遠在底層(layer='below') -- 🔒 事件點永遠可見可點擊 -- 🔒 文字框永遠清晰可讀 -- 🔒 hover 事件不會被線條攔截 - ---- - -## 優勢總結 - -### 1. **視覺清晰** -- 線條不會遮擋事件點 -- 文字框始終在最上層 -- 半透明效果減少視覺干擾 - -### 2. **交互友好** -- hover 事件正確觸發在事件點和文字框 -- 線條不攔截滑鼠事件 -- 用戶體驗更流暢 - -### 3. **技術優雅** -- 使用 Plotly 標準的 shape 系統 -- 明確的 layer 控制 -- SVG path 語法靈活高效 - -### 4. **與 v6.0 完全兼容** -- 保留泳道分配法的所有優點 -- 僅改變渲染方式,不改變邏輯 -- 100% 向後兼容 - ---- - -## 代碼位置 - -### 修改的文件 - -**`backend/renderer_timeline.py`** - -#### 水平時間軸(第 372-389 行) -```python -# 使用 shape line 繪製連接線(分段),設定 layer='below' 避免遮擋 -for i in range(len(line_x_points) - 1): - shapes.append({ - 'type': 'line', - 'x0': line_x_points[i], - 'y0': line_y_points[i], - 'x1': line_x_points[i + 1], - 'y1': line_y_points[i + 1], - 'xref': 'x', - 'yref': 'y', - 'line': { - 'color': marker['color'], - 'width': 1.5, - }, - 'layer': 'below', # 線條置於底層 - 'opacity': 0.7, # 半透明 - }) -``` - -#### 垂直時間軸(第 635-652 行) -- 相同的實現邏輯(迴圈繪製線段) -- 適配垂直時間軸的座標系統 - ---- - -## 測試方法 - -### 1. 啟動應用 -```bash -conda activate timeline_designer -python app.py -``` - -### 2. 訪問界面 -- 瀏覽器:http://localhost:8000 -- 或使用 PyWebview GUI 視窗 - -### 3. 測試示範檔案 -- `demo_project_timeline.csv` - 15 個事件 -- `demo_life_events.csv` - 11 個事件 -- `demo_product_roadmap.csv` - 14 個事件 - -### 4. 驗證重點 -- ✅ 連接線是否在底層(不遮擋事件點和文字框) -- ✅ 事件點 hover 是否正常觸發 -- ✅ 文字框是否清晰可見 -- ✅ 線條是否有半透明效果 -- ✅ 視覺是否整潔專業 - ---- - -## 與其他版本對比 - -| 版本 | 連接線方式 | 視覺遮擋 | hover 問題 | 複雜度 | 效果 | -|------|-----------|---------|-----------|--------|------| -| v5.0 | scatter + 碰撞檢測 | ⚠️ 可能遮擋 | ⚠️ 可能攔截 | 高 | 中等 | -| v6.0 | scatter + 泳道分配 | ⚠️ 可能遮擋 | ⚠️ 可能攔截 | 低 | 良好 | -| **v7.0** | **shape.path + layer='below'** | **✅ 無遮擋** | **✅ 無攔截** | **低** | **優秀** | - ---- - -## 可調整參數 - -### 線條透明度 -```python -# renderer_timeline.py 第 382 行和第 639 行 -'opacity': 0.7, # 預設 0.7,可調整為 0.5-1.0 -``` - -### 線條寬度 -```python -# renderer_timeline.py 第 378 行和第 635 行 -'width': 1.5, # 預設 1.5,可調整為 1.0-3.0 -``` - -### 線條樣式 -```python -'line': { - 'color': marker['color'], - 'width': 1.5, - 'dash': 'dot', # 可選:'solid', 'dot', 'dash', 'dashdot' -} -``` - ---- - -## 未來可能改進 - -### 1. **同日多卡片左右交錯** -- 同一天的卡片交錯使用左/右側邊當錨點 -- 水平段自然平行不打架 -- 需要在標籤定位邏輯中實現 - -### 2. **貝茲曲線平滑** -- 使用 SVG 的 C (Cubic Bezier) 命令 -- 更自然的曲線效果 -- 視覺更柔和 - -```python -# 範例:貝茲曲線路徑 -path_str = f"M {x0},{y0} C {cx1},{cy1} {cx2},{cy2} {x1},{y1}" -``` - -### 3. **動態線條顏色** -- 根據事件重要性調整透明度 -- 高優先級事件使用更鮮明的線條 -- 低優先級事件線條更淡 - ---- - -## 錯誤修復記錄 - -### Bug Fix #2: Shape.path 不支持 datetime 座標 - -**問題描述**: -- Plotly 的 `shape.path` 不直接支持 datetime 座標軸 -- 使用 path 命令(M, L)時,datetime 對象無法正確解析 -- 導致連接線完全不顯示 - -**修復方案**: -改用 `type='line'` 分段繪製,每一段連線作為獨立的 shape: - -```python -# 修復前:使用 path(不支持 datetime) -path_str = f"M {x0},{y0} L {x1},{y1} L {x2},{y2} L {x3},{y3}" -shapes.append({ - 'type': 'path', - 'path': path_str, - ... -}) - -# 修復後:使用多個 line segment(支持 datetime) -for i in range(len(line_x_points) - 1): - shapes.append({ - 'type': 'line', - 'x0': line_x_points[i], - 'y0': line_y_points[i], - 'x1': line_x_points[i + 1], - 'y1': line_y_points[i + 1], - 'xref': 'x', # 明確指定座標參考系統 - 'yref': 'y', - 'line': {'color': marker['color'], 'width': 1.5}, - 'layer': 'below', - 'opacity': 0.7, - }) -``` - -**技術細節**: -- L 形連接線需要 3 個線段:垂直 → 水平 → 垂直(或水平 → 垂直 → 水平) -- 直線連接只需要 1 個線段 -- 使用迴圈自動處理不同長度的點列表 - -**影響範圍**: -- 水平時間軸(`renderer_timeline.py` 第 372-389 行) -- 垂直時間軸(`renderer_timeline.py` 第 635-652 行) - -**優勢**: -- ✅ 完全支持 datetime 座標 -- ✅ 保持 `layer='below'` 的優點 -- ✅ 視覺效果與 path 完全相同 -- ✅ 代碼更簡潔(迴圈處理) - ---- - -### Bug Fix #1: 處理直線連接的索引錯誤 - -**問題描述**: -- 當標籤正好在事件點正上方/正側方時,使用直線連接(2 個點) -- 但 path_str 構建時嘗試訪問 4 個點的索引 [0] 到 [3] -- 導致 `list index out of range` 錯誤 - -**修復方案**: -```python -# 修復前:總是嘗試訪問 4 個索引 -path_str = f"M {line_x_points[0]},{line_y_points[0]} L {line_x_points[1]},{line_y_points[1]} L {line_x_points[2]},{line_y_points[2]} L {line_x_points[3]},{line_y_points[3]}" - -# 修復後:根據情況構建不同的 path -if is_directly_above: # 或 is_directly_sideways (垂直時間軸) - # 直線路徑(2 個點) - path_str = f"M {line_x_points[0]},{line_y_points[0]} L {line_x_points[1]},{line_y_points[1]}" -else: - # L 形路徑(4 個點) - path_str = f"M {line_x_points[0]},{line_y_points[0]} L {line_x_points[1]},{line_y_points[1]} L {line_x_points[2]},{line_y_points[2]} L {line_x_points[3]},{line_y_points[3]}" -``` - -**影響範圍**: -- 水平時間軸(`renderer_timeline.py` 第 373-378 行) -- 垂直時間軸(`renderer_timeline.py` 第 636-641 行) - -**測試驗證**: -- ✅ 後端服務正常啟動 -- ✅ health check 通過 -- ✅ 可以正常渲染時間軸 - ---- - -**版本**: v7.0 - **Shape Line Segments 渲染法** -**更新日期**: 2025-11-05 (包含 2 個 Bug Fix) -**作者**: Claude AI - -## 核心理念 - -> "正確的工具做正確的事" -> "Shape line segments for datetime compatibility" -> "Layer control is visual clarity" - -**從數據可視化到圖形設計,這是渲染方式的優雅轉變!** 🎨 - ---- - -## 總結 - -v7.0 成功將連接線從 scatter 轉換為 shape line segments 渲染: - -✅ **問題解決**: -- 線條不再遮擋事件點和文字框 -- 完美支持 datetime 座標軸 -- hover 事件正確觸發 - -✅ **技術優勢**: -- 使用 `layer='below'` 明確控制 z-index -- 分段繪製支持任意複雜路徑 -- 代碼簡潔(迴圈處理) - -✅ **完全兼容**: -- 保留 v6.0 泳道分配法的所有優點 -- 100% 保證線條不交錯 -- 視覺整潔專業 diff --git a/IMPROVEMENTS_v8.md b/IMPROVEMENTS_v8.md deleted file mode 100644 index a9bf14c..0000000 --- a/IMPROVEMENTS_v8.md +++ /dev/null @@ -1,454 +0,0 @@ -# 時間軸標籤避碰改進(v8.0) - 力導向演算法 - -## 核心轉變 - -### 從固定泳道到智能動態優化 - -**v7.0 的問題**: -- ⚠️ 泳道分配雖保證垂直分離,但水平方向仍可能擁擠 -- ⚠️ 多條線在同一時間區域經過時視覺混亂 -- ⚠️ 文字框背景遮擋連接線(95% 不透明) -- ⚠️ 無法動態調整以達到最佳布局 - -**v8.0 的解決方案 - 力導向演算法**: -- ✅ **使用物理模擬優化標籤位置** -- ✅ **排斥力:標籤之間互相推開** -- ✅ **吸引力:標籤被拉向事件點** -- ✅ **迭代收斂:自動達到平衡狀態** -- ✅ **降低文字框不透明度(85%)** - ---- - -## 技術實現 - -### 力導向演算法原理 - -**核心概念**: -- 將標籤視為物理粒子 -- 標籤之間存在排斥力(避免重疊) -- 標籤與事件點之間存在吸引力(彈簧連接) -- 通過多次迭代達到能量最低的平衡狀態 - -**數學模型**: - -```python -# 1. 排斥力(標籤之間) -repulsion = repulsion_strength / (distance^2) -force_x = (dx / distance) * repulsion -force_y = (dy / distance) * repulsion - -# 2. 吸引力(標籤與事件點之間) -attraction_x = (event_x - label_x) * attraction_strength -attraction_y = (event_y - label_y) * attraction_strength - -# 3. 速度更新(帶阻尼) -velocity = (velocity + force) * damping - -# 4. 位置更新 -position += velocity -``` - -### 算法參數 - -```python -max_iterations = 100 # 最大迭代次數 -repulsion_strength = 100.0 # 排斥力強度 -attraction_strength = 0.05 # 吸引力強度(彈簧係數) -damping = 0.7 # 阻尼係數(0-1,越小減速越快) -``` - -**參數說明**: -- **repulsion_strength**: 控制標籤之間的最小距離,值越大標籤越分散 -- **attraction_strength**: 控制標籤與事件點的連接強度,值越大標籤越靠近事件點 -- **damping**: 防止系統震盪,幫助快速收斂 - ---- - -## 算法流程 - -### 步驟詳解 - -```python -def apply_force_directed_layout(label_positions, config): - # 1. 初始化 - velocities = [{'x': 0, 'y': 0} for _ in label_positions] - - # 2. 迭代優化 - for iteration in range(max_iterations): - forces = [{'x': 0, 'y': 0} for _ in label_positions] - - # 3. 計算排斥力(所有標籤對) - for i in range(len(positions)): - for j in range(i + 1, len(positions)): - distance = sqrt(dx^2 + dy^2) - repulsion = repulsion_strength / (distance^2) - # 應用牛頓第三定律(作用力與反作用力) - forces[i] -= repulsion - forces[j] += repulsion - - # 4. 計算吸引力(標籤→事件點) - for i in range(len(positions)): - attraction = (event_pos - label_pos) * attraction_strength - forces[i] += attraction - - # 5. 更新速度和位置 - for i in range(len(positions)): - velocities[i] = (velocities[i] + forces[i]) * damping - positions[i] += velocities[i] - - # 限制 y 方向範圍(保持上下分離) - if positions[i].y > 0: - positions[i].y = max(0.5, min(positions[i].y, 10.0)) - else: - positions[i].y = min(-0.5, max(positions[i].y, -10.0)) - - # 6. 檢查收斂 - if max_displacement < 0.01: - break - - return optimized_positions -``` - ---- - -## 視覺效果 - -### 力導向優化前後對比 - -**優化前(v7.0 泳道分配)**: -``` - ┌────┐ ┌────┐ ┌────┐ - │ L1 │ │ L2 │ │ L3 │ ← 可能過於擁擠 - └────┘ └────┘ └────┘ - │ │ │ - │ │ │ ← 線條可能重疊 - ────┼─────────┼─────────┼──── - ● ● ● -``` - -**優化後(v8.0 力導向)**: -``` - ┌────┐ ┌────┐ - │ L1 │ │ L3 │ ← 自動分散 - └────┘ └────┘ - │ ┌────┐ │ - │ │ L2 │ │ ← 動態調整位置 - │ └────┘ │ - │ │ │ ← 線條自然分離 - ────┼────────────┼──────┼──── - ● ● ● -``` - -### 力的作用示意 - -``` -排斥力 (標籤之間): - ┌────┐ ←→ ┌────┐ - │ L1 │ 推開 │ L2 │ - └────┘ └────┘ - -吸引力 (標籤與事件點): - ┌────┐ - │ L1 │ - └──↓─┘ 彈簧拉力 - ● 事件點 -``` - ---- - -## 關鍵改進 - -### 1. 修復文字框遮擋問題 - -**問題**: -- 文字框使用 `rgba(255, 255, 255, 0.95)` 背景 -- 95% 不透明會完全遮擋底層連接線 - -**解決**: -```python -# 修改前 -'bgcolor': 'rgba(255, 255, 255, 0.95)' - -# 修改後 -'bgcolor': 'rgba(255, 255, 255, 0.85)' # 降低到 85% -``` - -### 2. 實現力導向布局 - -**架構**: -- 獨立函數 `apply_force_directed_layout()` (第 23-153 行) -- 在生成 markers 後、繪製前調用 -- 支持水平和垂直時間軸 - -**調用位置**: -```python -# 水平時間軸(第 432-441 行) -if config.enable_zoom: # 使用 enable_zoom 作為開關 - markers = apply_force_directed_layout(markers, config, ...) - -# 垂直時間軸(第 693-702 行) -if config.enable_zoom: - markers = apply_force_directed_layout(markers, config, ...) -``` - ---- - -## 性能分析 - -### 時間複雜度 - -| 操作 | 複雜度 | 說明 | -|------|--------|------| -| 排斥力計算 | O(n²) | 每對標籤都要計算 | -| 吸引力計算 | O(n) | 每個標籤獨立計算 | -| 位置更新 | O(n) | 每個標籤獨立更新 | -| **總計(每次迭代)** | **O(n²)** | 主要瓶頸在排斥力 | -| **總計(100次迭代)** | **O(100n²)** | 通常會提前收斂 | - -### 實際性能 - -``` -事件數量:10 → 迭代時間:<0.01秒 -事件數量:50 → 迭代時間:<0.1秒 -事件數量:100 → 迭代時間:<0.5秒 -``` - -**優化空間**: -- 可使用空間索引(Quadtree)將排斥力計算降到 O(n log n) -- 可使用 Barnes-Hut 近似算法加速大規模場景 -- 通常在 20-50 次迭代後就會收斂 - ---- - -## 收斂檢測 - -```python -# 計算每個標籤的位移 -displacement = sqrt((new_x - old_x)^2 + (new_y - old_y)^2) - -# 檢查最大位移 -if max(displacements) < 0.01: - logger.info(f"力導向演算法在第 {iteration + 1} 次迭代後收斂") - break -``` - -**典型收斂曲線**: -``` -迭代次數 最大位移 - 0 100.0 - 10 50.2 - 20 15.3 - 30 3.1 - 40 0.5 - 50 0.08 - 60 0.005 ← 收斂! -``` - ---- - -## 參數調整指南 - -### 如果標籤太分散(遠離事件點) - -```python -# 增加吸引力 -attraction_strength = 0.1 # 從 0.05 增加到 0.1 - -# 或減少排斥力 -repulsion_strength = 50.0 # 從 100.0 減少到 50.0 -``` - -### 如果標籤仍然重疊 - -```python -# 增加排斥力 -repulsion_strength = 200.0 # 從 100.0 增加到 200.0 - -# 或增加迭代次數 -max_iterations = 200 # 從 100 增加到 200 -``` - -### 如果系統震盪不穩定 - -```python -# 增加阻尼(更快減速) -damping = 0.5 # 從 0.7 減少到 0.5 -``` - ---- - -## 與其他版本對比 - -| 版本 | 方法 | 連接線重疊 | 文字框遮擋 | 性能 | 適應性 | -|------|------|-----------|-----------|------|--------| -| v6.0 | 泳道分配 | ⚠️ 可能 | ❌ 嚴重 | ⚡ 極快 O(n) | ❌ 固定 | -| v7.0 | Shape分段渲染 | ⚠️ 可能 | ⚠️ 仍有 | ⚡ 極快 O(n) | ❌ 固定 | -| **v8.0** | **力導向優化** | **✅ 極少** | **✅ 改善** | **⚡ 中等 O(n²)** | **✅ 動態** | - ---- - -## 啟用方式 - -**當前實現**(臨時): -- 使用 `config.enable_zoom` 作為力導向演算法的開關 -- 啟用縮放功能時自動應用力導向優化 - -**未來改進**: -- 添加專用配置項 `config.enable_force_directed` -- 允許用戶自定義力的參數 - -```python -# 未來配置範例 -config = TimelineConfig( - enable_force_directed=True, - force_directed_params={ - 'max_iterations': 100, - 'repulsion_strength': 100.0, - 'attraction_strength': 0.05, - 'damping': 0.7 - } -) -``` - ---- - -## 代碼位置 - -### 新增函數 - -**`backend/renderer_timeline.py`** (第 23-153 行) - -```python -def apply_force_directed_layout( - label_positions: List[Dict], - config: 'TimelineConfig', - max_iterations: int = 100, - repulsion_strength: float = 100.0, - attraction_strength: float = 0.05, - damping: float = 0.7 -) -> List[Dict]: - """ - 使用力導向演算法優化標籤位置 - - 模擬物理系統: - - 標籤之間:排斥力(F = k / d²) - - 標籤與事件點:吸引力(F = k * d) - - 速度阻尼:防止震盪 - """ - # ... 詳見代碼 ... -``` - -### 調用位置 - -**水平時間軸** (第 432-441 行): -```python -if config.enable_zoom: - markers = apply_force_directed_layout( - markers, config, - max_iterations=100, - repulsion_strength=100.0, - attraction_strength=0.05, - damping=0.7 - ) -``` - -**垂直時間軸** (第 693-702 行): -```python -if config.enable_zoom: - markers = apply_force_directed_layout( - markers, config, - max_iterations=100, - repulsion_strength=100.0, - attraction_strength=0.05, - damping=0.7 - ) -``` - ---- - -## 測試方法 - -### 1. 啟動應用 -```bash -conda activate timeline_designer -python app.py -``` - -### 2. 訪問界面 -- GUI 視窗會自動開啟 -- 或訪問 http://localhost:8000 - -### 3. 測試示範檔案 -載入以下檔案並觀察效果: -- `demo_project_timeline.csv` - 15 個事件 -- `demo_life_events.csv` - 11 個事件 -- `demo_product_roadmap.csv` - 14 個事件 - -### 4. 驗證重點 -- ✅ 標籤是否自動分散(不擁擠) -- ✅ 連接線是否不再重疊 -- ✅ 文字框背景是否不完全遮擋線條 -- ✅ 標籤是否保持靠近事件點 -- ✅ 渲染速度是否可接受(< 1秒) - -### 5. 查看日誌 -``` -力導向演算法在第 XX 次迭代後收斂 -``` - ---- - -## 未來改進方向 - -### 1. **Barnes-Hut 近似算法** -- 使用 Quadtree 空間劃分 -- 將遠距離標籤群視為單一質點 -- 降低複雜度到 O(n log n) - -### 2. **考慮文字框尺寸** -- 當前只考慮標籤中心點 -- 應考慮文字框的實際寬度和高度 -- 使用 OBB(有向包圍盒)碰撞檢測 - -### 3. **分層力導向** -- 先在層級內部優化 -- 再在層級之間優化 -- 減少計算量並保持層級結構 - -### 4. **動畫過渡** -- 記錄每次迭代的位置 -- 在前端播放優化過程動畫 -- 提供更好的視覺反饋 - ---- - -**版本**: v8.0 - **力導向演算法** -**更新日期**: 2025-11-05 -**作者**: Claude AI - -## 核心理念 - -> "讓物理定律解決佈局問題" -> "力導向演算法:優雅、自然、有效" -> "從啟發式規則到物理模擬" - -## 總結 - -v8.0 成功整合力導向演算法,實現智能標籤佈局優化: - -✅ **問題解決**: -- 標籤自動分散,避免擁擠 -- 連接線重疊大幅減少 -- 文字框不再完全遮擋線條 - -✅ **技術優勢**: -- 使用成熟的物理模擬方法 -- 自動達到平衡狀態(收斂) -- 可調整參數適應不同場景 - -✅ **兼容性**: -- 保留 v6.0 泳道分配的優點 -- 保留 v7.0 shape 分段渲染 -- 添加動態優化層 - -**從固定規則到自適應優化,這是布局算法的質的飛躍!** 🚀 diff --git a/IMPROVEMENTS_v9.md b/IMPROVEMENTS_v9.md deleted file mode 100644 index 3440f62..0000000 --- a/IMPROVEMENTS_v9.md +++ /dev/null @@ -1,369 +0,0 @@ -# 時間軸標籤避碰改進(v9.0) - 固定5泳道 + 貪婪避讓算法 - -## 核心轉變 - -### 從動態層級到固定5泳道 + 智能分配 - -**v8.0 的問題**: -- ❌ D3 Force 雖然避碰好,但實際效果不理想 -- ❌ 標籤移動幅度大,視覺混亂 -- ❌ 邊緣截斷問題難以完全解決 - -**v9.0 的解決方案 - 回歸 Plotly + 智能優化**: -- ✅ **固定 5 個泳道**(上方 3 個 + 下方 2 個) -- ✅ **貪婪算法選擇最佳泳道** -- ✅ **考慮連接線遮擋** -- ✅ **考慮文字框重疊** - ---- - -## 技術實現 - -### 1. 固定 5 泳道配置 - -```python -# 固定 5 個泳道 -SWIM_LANES = [ - {'index': 0, 'side': 'upper', 'ratio': 0.25}, # 上方泳道 1(最低) - {'index': 1, 'side': 'upper', 'ratio': 0.55}, # 上方泳道 2(中) - {'index': 2, 'side': 'upper', 'ratio': 0.85}, # 上方泳道 3(最高) - {'index': 3, 'side': 'lower', 'ratio': 0.25}, # 下方泳道 1(最低) - {'index': 4, 'side': 'lower', 'ratio': 0.55}, # 下方泳道 2(最高) -] -``` - -### 2. 貪婪算法選擇泳道 - -```python -def greedy_lane_assignment(event, occupied_lanes): - """ - 為事件選擇最佳泳道 - - 考慮因素: - 1. 文字框水平重疊 - 2. 連接線垂直交叉 - 3. 優先選擇碰撞最少的泳道 - """ - best_lane = None - min_conflicts = float('inf') - - for lane_id in range(5): - conflicts = calculate_conflicts(event, lane_id, occupied_lanes) - if conflicts < min_conflicts: - min_conflicts = conflicts - best_lane = lane_id - - return best_lane -``` - -### 3. 衝突計算 - -```python -def calculate_conflicts(event, lane_id, occupied_lanes): - """ - 計算選擇特定泳道的衝突數量 - - Returns: - conflict_score: 衝突分數(越低越好) - """ - score = 0 - - # 檢查文字框水平重疊 - for occupied in occupied_lanes[lane_id]: - if text_boxes_overlap(event, occupied): - score += 10 # 重疊權重高 - - # 檢查連接線交叉 - for other_lane_id in range(5): - if other_lane_id == lane_id: - continue - for occupied in occupied_lanes[other_lane_id]: - if connection_lines_cross(event, lane_id, occupied, other_lane_id): - score += 1 # 交叉權重低 - - return score -``` - ---- - -## ✅ 實施代碼(已完成 + L型避讓增強) - -### 檔案:`backend/renderer_timeline.py` - -#### 🆕 連接線避開文字框功能(v9.0 增強) - -**核心思路**:在**貪婪算法選擇泳道時**就檢測連接線是否會穿過其他標籤,優先選擇不會穿過的泳道。 - -**新增方法**:`_check_line_intersects_textbox()` (第 460-513 行) -```python -def _check_line_intersects_textbox(self, line_x1, line_y1, line_x2, line_y2, - textbox_center_x, textbox_center_y, - textbox_width, textbox_height): - """檢測線段是否與文字框相交""" - - # 檢查水平線段是否穿過文字框 - if abs(line_y1 - line_y2) < 0.01: - if box_bottom <= line_y <= box_top: - if not (line_x_max < box_left or line_x_min > box_right): - return True - - # 檢查垂直線段是否穿過文字框 - if abs(line_x1 - line_x2) < 0.01: - if box_left <= line_x <= box_right: - if not (line_y_max < box_bottom or line_y_min > box_top): - return True - - return False -``` - -**增強的衝突分數計算**(第 345-458 行): -在貪婪算法中增加"連接線穿過其他文字框"的檢測: - -```python -def _calculate_lane_conflicts(self, ...): - # 1. 文字框水平重疊(高權重:10.0) - for occupied in occupied_lanes[lane_idx]: - if 重疊: - score += 10.0 * overlap_ratio - - # 2. 連接線穿過其他文字框(高權重:8.0)✨ 新增 - # 檢查連接線的三段路徑是否會穿過已有標籤 - for occupied in all_occupied_lanes: - # 檢查垂直線段1 - if self._check_line_intersects_textbox(event_x, 0, event_x, mid_y, ...): - score += 8.0 - - # 檢查水平線段 - if self._check_line_intersects_textbox(event_x, mid_y, label_x, mid_y, ...): - score += 8.0 - - # 檢查垂直線段2 - if self._check_line_intersects_textbox(label_x, mid_y, label_x, label_y, ...): - score += 8.0 - - # 3. 連接線交叉(低權重:1.0) - if 不同側 and 時間重疊: - score += 1.0 -``` - -**結果**:貪婪算法會自動選擇連接線不穿過其他標籤的泳道,大幅改善視覺清晰度。 - -#### 1. 新增 `_calculate_label_positions()` 方法(第 250-343 行) -```python -def _calculate_label_positions(self, events, start_date, end_date): - """v9.0 - 固定5泳道 + 貪婪避讓算法""" - - # 固定 5 個泳道配置 - SWIM_LANES = [ - {'index': 0, 'side': 'upper', 'ratio': 0.25}, - {'index': 1, 'side': 'upper', 'ratio': 0.55}, - {'index': 2, 'side': 'upper', 'ratio': 0.85}, - {'index': 3, 'side': 'lower', 'ratio': 0.25}, - {'index': 4, 'side': 'lower', 'ratio': 0.55}, - ] - - # 追蹤每個泳道的佔用情況 - occupied_lanes = {i: [] for i in range(5)} - - # 貪婪算法:按時間順序處理每個事件 - for event_idx, event in enumerate(events): - # 計算標籤時間範圍 - label_start = event_seconds - label_width_seconds / 2 - safety_margin - label_end = event_seconds + label_width_seconds / 2 + safety_margin - - # 為該事件選擇最佳泳道 - best_lane = None - min_conflicts = float('inf') - - for lane_config in SWIM_LANES: - conflict_score = self._calculate_lane_conflicts(...) - if conflict_score < min_conflicts: - min_conflicts = conflict_score - best_lane = lane_config - - # 記錄佔用情況並返回結果 - occupied_lanes[lane_idx].append({...}) - result.append({'swim_lane': lane_idx, ...}) -``` - -#### 2. 新增 `_calculate_lane_conflicts()` 方法(第 345-413 行) -```python -def _calculate_lane_conflicts(self, event_x, label_start, label_end, - lane_idx, lane_config, occupied_lanes, - total_seconds): - """計算將事件放置在特定泳道的衝突分數""" - - score = 0.0 - - # 1. 檢查同泳道的文字框水平重疊(高權重:10.0) - for occupied in occupied_lanes[lane_idx]: - if not (label_end < occupied['start'] or label_start > occupied['end']): - overlap_ratio = ... - score += 10.0 * overlap_ratio - - # 2. 檢查與其他泳道的連接線交叉(低權重:1.0) - for other_lane_idx in range(5): - for occupied in occupied_lanes[other_lane_idx]: - if 時間範圍重疊 and 在不同側: - score += 1.0 # 交叉權重低 - - return score -``` - -#### 3. 更新 `_render_horizontal()` 方法 -- **第 463-483 行**:使用新的泳道數據結構 - ```python - label_positions = self._calculate_label_positions(events, start_date, end_date) - - for i, event in enumerate(events): - pos_info = label_positions[i] - swim_lane = pos_info['swim_lane'] - swim_lane_config = pos_info['swim_lane_config'] - label_y = pos_info['label_y'] # 預先計算的 Y 座標 - ``` - -- **第 499-509 行**:更新 marker 數據結構 - ```python - markers.append({ - 'event_x': event_date, - 'label_x': label_x, - 'label_y': label_y, # v9.0 使用預先計算的 Y 座標 - 'swim_lane': swim_lane, - 'swim_lane_config': swim_lane_config, - ... - }) - ``` - -- **第 559-591 行**:使用固定泳道 ratio 計算連接線 - ```python - lane_ratio = swim_lane_config['ratio'] - mid_y = label_y * lane_ratio - ``` - -- **第 630-634 行**:固定 Y 軸範圍 - ```python - y_range_max = 3.5 # 上方最高層 + 邊距 - y_range_min = -2.5 # 下方最低層 + 邊距 - ``` - ---- - -## 🧪 測試驗證 - -### 測試步驟 - -1. **啟動應用程式** - ```bash - python app.py - ``` - -2. **導入測試資料** - - 使用 `demo_project_timeline.csv`(15 個事件) - - 或使用 `demo_life_events.csv`(11 個事件) - -3. **生成時間軸** - - 選擇 Plotly 渲染模式 - - 點擊「生成時間軸」按鈕 - -4. **觀察效果** - - ✅ 檢查是否有 5 個固定泳道 - - ✅ 檢查文字框是否無重疊 - - ✅ 檢查連接線是否交叉最少 - - ✅ 檢查視覺效果是否清晰 - ---- - -## 📊 v9.0 與前版本對比 - -| 項目 | v8.0 (D3 Force) | v9.0 (固定5泳道 + 貪婪算法) | -|------|----------------|---------------------------| -| **泳道數量** | 動態(無限制) | 固定 5 個 | -| **標籤分配** | 力導向模擬 | 貪婪算法 | -| **避碰策略** | 物理碰撞力 | 衝突分數計算 | -| **文字框重疊** | ❌ 偶爾發生 | ✅ 高權重避免(10.0) | -| **連接線交叉** | ❌ 較多 | ✅ 低權重優化(1.0) | -| **計算複雜度** | O(n² × iterations) | O(n × 5) = O(n) | -| **視覺穩定性** | ⚠️ 不穩定(動態) | ✅ 穩定(固定) | -| **可預測性** | ❌ 低 | ✅ 高 | - ---- - -## 🎯 v9.0 優勢 - -1. **固定泳道** - 視覺穩定,易於理解 -2. **貪婪算法** - 快速高效,O(n) 複雜度 -3. **衝突分數** - 精確控制重疊和交叉的優先級 -4. **可調優** - 簡單調整權重即可改變行為 -5. **回歸 Plotly** - 成熟穩定的渲染引擎 -6. **🆕 連接線避讓** - 選擇泳道時避免連接線穿過標籤,視覺清晰 - ---- - -## 🔧 參數調整(可選) - -如需調整避讓行為,可修改 `_calculate_lane_conflicts()` 方法中的權重: - -```python -# 文字框重疊權重(默認:10.0) -score += 10.0 * overlap_ratio - -# 連接線穿過文字框權重(默認:8.0)✨ 新增 -score += 8.0 - -# 連接線交叉權重(默認:1.0) -score += 1.0 - -# 同側遮擋權重(默認:0.5) -score += 0.5 -``` - -**建議**: -- 文字框重疊權重 10.0:最高優先級,必須避免 -- 連接線穿過文字框 8.0:次高優先級,嚴重影響可讀性 -- 連接線交叉權重 1.0:低優先級,視覺影響小 -- 保持比例 10:8:1:0.5 通常效果最佳 - ---- - -## ✅ 實施總結 - -- **實施時間**:約 2 小時 -- **修改檔案**:1 個(`backend/renderer_timeline.py`) -- **新增方法**:3 個 - - `_calculate_label_positions()` - 固定5泳道 + 貪婪算法 - - `_calculate_lane_conflicts()` - 衝突分數計算(含連接線穿過檢測) - - `_check_line_intersects_textbox()` - 線段與文字框碰撞檢測 -- **程式碼行數**:約 280 行 -- **測試狀態**:待驗證 - -**v9.0 已完成(含連接線避讓增強)!現在請啟動應用並測試效果。** 🎉 - ---- - -## 🎨 連接線避讓示意圖 - -### 問題場景 -``` -標籤B - ↑ - | - |─────────[標籤A]─────→ 標籤A - | 遮擋! ↑ - | | -●─────────────────────● -事件點B 事件點A -``` -**問題**:標籤B的連接線(水平線段)穿過標籤A的文字框 - -### v9.0 解決方案 -``` -標籤B 標籤A - ↑ ↑ - | | - |────→ ←─────| (較高泳道) - | | - | [標籤A] | -●───────────────────● -事件點B 事件點A -``` -**解決**:貪婪算法讓標籤B選擇較高泳道,連接線不穿過標籤A diff --git a/MIGRATION_TO_D3_FORCE.md b/MIGRATION_TO_D3_FORCE.md deleted file mode 100644 index b27e9eb..0000000 --- a/MIGRATION_TO_D3_FORCE.md +++ /dev/null @@ -1,494 +0,0 @@ -# 遷移到 D3.js Force-Directed Layout - 實施計劃 - -## 📋 目標 - -將時間軸標籤避讓邏輯從**後端 Plotly**遷移到**前端 D3.js d3-force**,實現專業的標籤碰撞避讓。 - ---- - -## 🏗️ 架構變更 - -### 當前架構(v7.0) -``` -┌─────────┐ 事件資料 ┌─────────┐ Plotly圖表 ┌─────────┐ -│ Python │ --------> │ 計算 │ ----------> │ React │ -│ 後端 │ │ 標籤位置 │ │ 前端 │ -└─────────┘ └─────────┘ └─────────┘ - ❌ 標籤避讓在這裡(效果差) -``` - -### 新架構(D3 Force) -``` -┌─────────┐ 事件資料 ┌─────────────┐ 渲染座標 ┌─────────┐ -│ Python │ --------> │ D3 Force │ ---------> │ React │ -│ 後端 │ (乾淨) │ 標籤避讓 │ │ 前端 │ -└─────────┘ └─────────────┘ └─────────┘ - ✅ 力導向演算法在這裡 -``` - ---- - -## 📦 步驟 1: 安裝 D3.js 依賴 - -```bash -cd frontend-react -npm install d3 d3-force d3-scale d3-axis d3-selection -npm install --save-dev @types/d3 -``` - -**安裝的模組**: -- `d3-force`: 力導向布局核心 -- `d3-scale`: 時間軸刻度 -- `d3-axis`: 軸線繪製 -- `d3-selection`: DOM 操作 - ---- - -## 🔧 步驟 2: 修改後端 API - -### 2.1 新增端點:返回原始事件資料 - -**檔案**: `backend/main.py` - -```python -@router.get("/api/events/raw") -async def get_raw_events(): - """ - 返回原始事件資料(不做任何布局計算) - 供前端 D3.js 使用 - """ - events = event_manager.get_events() - return { - "success": True, - "events": [ - { - "id": i, - "start": event.start.isoformat(), - "end": event.end.isoformat() if event.end else None, - "title": event.title, - "description": event.description, - "color": event.color or "#3B82F6", - "layer": event.layer - } - for i, event in enumerate(events) - ], - "count": len(events) - } -``` - -### 2.2 保留 Plotly 端點作為備選 - -```python -@router.post("/api/render") # 保留舊版 -@router.post("/api/render/plotly") # 明確標記 -async def render_plotly_timeline(config: TimelineConfig): - # ... 現有代碼 ... -``` - ---- - -## 🎨 步驟 3: 創建 D3 時間軸組件 - -### 3.1 創建組件文件 - -**檔案**: `frontend-react/src/components/D3Timeline.tsx` - -```typescript -import { useEffect, useRef, useState } from 'react'; -import * as d3 from 'd3'; - -interface Event { - id: number; - start: string; - end?: string; - title: string; - description: string; - color: string; - layer: number; -} - -interface D3TimelineProps { - events: Event[]; - width?: number; - height?: number; -} - -interface Node extends d3.SimulationNodeDatum { - id: number; - type: 'event' | 'label'; - eventId: number; - x: number; - y: number; - fx?: number | null; // 固定 X(事件點) - fy?: number | null; // 固定 Y(事件點在時間軸上) - event: Event; - labelWidth: number; - labelHeight: number; -} - -export default function D3Timeline({ events, width = 1200, height = 600 }: D3TimelineProps) { - const svgRef = useRef(null); - const [simulation, setSimulation] = useState | null>(null); - - useEffect(() => { - if (!svgRef.current || events.length === 0) return; - - // 清空 SVG - const svg = d3.select(svgRef.current); - svg.selectAll('*').remove(); - - // 邊距設定 - const margin = { top: 100, right: 50, bottom: 50, left: 50 }; - const innerWidth = width - margin.left - margin.right; - const innerHeight = height - margin.top - margin.bottom; - - // 創建主 group - const g = svg - .append('g') - .attr('transform', `translate(${margin.left},${margin.top})`); - - // 時間範圍 - const dates = events.map(e => new Date(e.start)); - const xScale = d3.scaleTime() - .domain([d3.min(dates)!, d3.max(dates)!]) - .range([0, innerWidth]); - - // 時間軸線 - const axisY = innerHeight / 2; - g.append('line') - .attr('x1', 0) - .attr('x2', innerWidth) - .attr('y1', axisY) - .attr('y2', axisY) - .attr('stroke', '#3B82F6') - .attr('stroke-width', 3); - - // 準備節點資料 - const nodes: Node[] = []; - - events.forEach((event, i) => { - const eventX = xScale(new Date(event.start)); - - // 事件點節點(固定位置) - nodes.push({ - id: i * 2, - type: 'event', - eventId: i, - x: eventX, - y: axisY, - fx: eventX, // 固定 X - 保證時間準確性 - fy: axisY, // 固定 Y - 在時間軸上 - event, - labelWidth: 0, - labelHeight: 0 - }); - - // 標籤節點(可移動) - const labelWidth = Math.max(event.title.length * 8, 120); - const labelHeight = 60; - const initialY = event.layer % 2 === 0 ? axisY - 150 : axisY + 150; - - nodes.push({ - id: i * 2 + 1, - type: 'label', - eventId: i, - x: eventX, // 初始 X 接近事件點 - y: initialY, // 初始 Y 根據層級 - fx: null, - fy: null, - event, - labelWidth, - labelHeight - }); - }); - - // 連接線(標籤 → 事件點) - const links = nodes - .filter(n => n.type === 'label') - .map(label => ({ - source: label.id, - target: label.id - 1 // 對應的事件點 - })); - - // D3 力導向模擬 - const sim = d3.forceSimulation(nodes) - // 1. 碰撞力:標籤之間互相推開 - .force('collide', d3.forceCollide() - .radius(d => { - if (d.type === 'label') { - // 使用橢圓碰撞半徑(考慮文字框寬高) - return Math.max(d.labelWidth / 2, d.labelHeight / 2) + 10; - } - return 5; // 事件點不參與碰撞 - }) - .strength(0.8) - ) - // 2. 連結力:標籤拉向事件點(像彈簧) - .force('link', d3.forceLink(links) - .id(d => (d as Node).id) - .distance(100) // 理想距離 - .strength(0.3) // 彈簧強度 - ) - // 3. X 方向定位力:標籤靠近事件點的 X 座標 - .force('x', d3.forceX(d => { - if (d.type === 'label') { - const eventNode = nodes.find(n => n.type === 'event' && n.eventId === d.eventId); - return eventNode ? eventNode.x : d.x; - } - return d.x; - }).strength(0.5)) - // 4. Y 方向定位力:標籤保持在上方或下方 - .force('y', d3.forceY(d => { - if (d.type === 'label') { - return d.y < axisY ? axisY - 120 : axisY + 120; - } - return axisY; - }).strength(0.3)) - // 5. 邊界限制 - .on('tick', () => { - nodes.forEach(d => { - if (d.type === 'label') { - // 限制 Y 範圍 - if (d.y! < 20) d.y = 20; - if (d.y! > innerHeight - 20) d.y = innerHeight - 20; - - // 限制 X 範圍(允許小幅偏移) - const eventNode = nodes.find(n => n.type === 'event' && n.eventId === d.eventId)!; - const maxOffset = 80; - if (Math.abs(d.x! - eventNode.x!) > maxOffset) { - d.x = eventNode.x! + (d.x! > eventNode.x! ? maxOffset : -maxOffset); - } - } - }); - - updateVisualization(); - }); - - setSimulation(sim); - - // 繪製可視化元素 - function updateVisualization() { - // 連接線 - const linkElements = g.selectAll('.link') - .data(links) - .join('line') - .attr('class', 'link') - .attr('x1', d => { - const source = nodes.find(n => n.id === (typeof d.source === 'number' ? d.source : d.source.id))!; - return source.x!; - }) - .attr('y1', d => { - const source = nodes.find(n => n.id === (typeof d.source === 'number' ? d.source : d.source.id))!; - return source.y!; - }) - .attr('x2', d => { - const target = nodes.find(n => n.id === (typeof d.target === 'number' ? d.target : d.target.id))!; - return target.x!; - }) - .attr('y2', d => { - const target = nodes.find(n => n.id === (typeof d.target === 'number' ? d.target : d.target.id))!; - return target.y!; - }) - .attr('stroke', '#94a3b8') - .attr('stroke-width', 1.5) - .attr('opacity', 0.7); - - // 事件點 - const eventNodes = g.selectAll('.event-node') - .data(nodes.filter(n => n.type === 'event')) - .join('circle') - .attr('class', 'event-node') - .attr('cx', d => d.x!) - .attr('cy', d => d.y!) - .attr('r', 8) - .attr('fill', d => d.event.color) - .attr('stroke', '#fff') - .attr('stroke-width', 2); - - // 標籤文字框 - const labelGroups = g.selectAll('.label-group') - .data(nodes.filter(n => n.type === 'label')) - .join('g') - .attr('class', 'label-group') - .attr('transform', d => `translate(${d.x! - d.labelWidth / 2},${d.y! - d.labelHeight / 2})`); - - // 文字框背景 - labelGroups.selectAll('rect') - .data(d => [d]) - .join('rect') - .attr('width', d => d.labelWidth) - .attr('height', d => d.labelHeight) - .attr('rx', 6) - .attr('fill', 'white') - .attr('opacity', 0.9) - .attr('stroke', d => d.event.color) - .attr('stroke-width', 2); - - // 文字內容 - labelGroups.selectAll('text') - .data(d => [d]) - .join('text') - .attr('x', d => d.labelWidth / 2) - .attr('y', 20) - .attr('text-anchor', 'middle') - .attr('font-size', 12) - .attr('font-weight', 'bold') - .text(d => d.event.title); - } - - // 初始繪製 - updateVisualization(); - - // 清理函數 - return () => { - sim.stop(); - }; - }, [events, width, height]); - - return ( -
- -
- ); -} -``` - ---- - -## 🔗 步驟 4: 整合到 App.tsx - -**檔案**: `frontend-react/src/App.tsx` - -```typescript -import { useState, useCallback } from 'react'; -import D3Timeline from './components/D3Timeline'; -import { timelineAPI } from './api/timeline'; - -function App() { - const [events, setEvents] = useState([]); - const [renderMode, setRenderMode] = useState<'d3' | 'plotly'>('d3'); - - // ... 其他現有代碼 ... - - // 載入原始事件資料(for D3) - const loadEventsForD3 = async () => { - try { - const response = await axios.get('http://localhost:8000/api/events/raw'); - if (response.data.success) { - setEvents(response.data.events); - } - } catch (error) { - console.error('Failed to load events:', error); - } - }; - - return ( -
- {/* ... 現有代碼 ... */} - - {/* 渲染模式切換 */} -
-
- - -
- - {renderMode === 'd3' && events.length > 0 && ( - - )} - - {renderMode === 'plotly' && plotlyData && ( - - )} -
-
- ); -} -``` - ---- - -## 🎯 關鍵技術點 - -### 1. 固定事件點位置 -```typescript -{ - fx: eventX, // 固定 X - 保證時間準確性 - fy: axisY, // 固定 Y - 在時間軸上 -} -``` - -### 2. 碰撞力(避免重疊) -```typescript -.force('collide', d3.forceCollide() - .radius(d => Math.max(d.labelWidth / 2, d.labelHeight / 2) + 10) - .strength(0.8) -) -``` - -### 3. 連結力(彈簧效果) -```typescript -.force('link', d3.forceLink(links) - .distance(100) // 理想距離 - .strength(0.3) // 彈簧強度 -) -``` - -### 4. 限制標籤 X 偏移 -```typescript -const maxOffset = 80; -if (Math.abs(labelX - eventX) > maxOffset) { - labelX = eventX + (labelX > eventX ? maxOffset : -maxOffset); -} -``` - ---- - -## 📊 優勢對比 - -| 項目 | Plotly 後端 | D3 Force 前端 | -|------|------------|---------------| -| 標籤避讓效果 | ⚠️ 差 | ✅ 專業 | -| 動態調整 | ❌ 需重新渲染 | ✅ 即時模擬 | -| 性能 | ⚠️ 後端計算 | ✅ 瀏覽器端 | -| 可定制性 | ❌ 有限 | ✅ 完全控制 | -| 開發成本 | ✅ 低 | ⚠️ 中等 | - ---- - -## ⏱️ 實施時程 - -- **步驟 1**: 安裝依賴 - **15分鐘** -- **步驟 2**: 修改後端 API - **30分鐘** -- **步驟 3**: 創建 D3 組件 - **2-3小時** -- **步驟 4**: 整合到 App - **1小時** -- **測試調優**: **1-2小時** - -**總計**: **半天到一天** - ---- - -## 🚀 下一步 - -您希望我: - -**A.** 立即開始實施(按步驟執行) -**B.** 先創建一個簡化版 POC 測試效果 -**C.** 評估其他替代方案(vis-timeline, react-calendar-timeline) - -請告訴我您的選擇! diff --git a/SDD.md b/SDD.md deleted file mode 100644 index a283077..0000000 --- a/SDD.md +++ /dev/null @@ -1,72 +0,0 @@ -# 📗 System Design Document (SDD) - -## 1. 架構概述 -``` -PyWebview Host - ├── FastAPI Backend - │ ├── importer.py(CSV/XLSX 處理) - │ ├── renderer.py(Plotly/kaleido 渲染) - │ ├── schemas.py(資料模型定義) - │ └── export.py(PDF/SVG/PNG 輸出) - └── Frontend (React + Tailwind) - ├── TimelineCanvas(vis-timeline 封裝) - ├── EventForm / ThemePanel / ExportDialog - └── api.ts(API 呼叫) -``` - -## 2. 資料模型 -```python -class Event(BaseModel): - id: str - title: str - start: datetime - end: Optional[datetime] - group: Optional[str] - description: Optional[str] - color: Optional[str] - -class TimelineConfig(BaseModel): - direction: Literal['horizontal', 'vertical'] = 'horizontal' - theme: str = 'modern' - show_grid: bool = True - -class ExportOptions(BaseModel): - fmt: Literal['png', 'pdf', 'svg'] - dpi: int = 300 -``` - -## 3. API 定義 -| Method | Endpoint | 功能 | 輸入 | 輸出 | -|---------|-----------|------|------|------| -| POST | /import | 匯入事件資料 | CSV/XLSX | Event[] | -| GET | /events | 取得事件列表 | None | Event[] | -| POST | /render | 生成時間軸 JSON | TimelineConfig | Plotly JSON | -| POST | /export | 導出時間軸圖 | ExportOptions | 圖檔 | -| GET | /themes | 主題列表 | None | Theme[] | - -## 4. 視覺化邏輯 -- 自動調整時間刻度(日/週/月) -- 重疊節點避碰算法 -- 拖曳吸附(Snap to Grid) -- Hover 顯示 Tooltip 詳細資訊 - -## 5. 前端契約 -```tsx -{}} - onMove={(id,newStart)=>{}} -/> -``` - -## 6. 系統相依性 -| 模組 | 用途 | -|------|------| -| PyWebview | 原生 GUI 容器 | -| FastAPI | 後端 API 框架 | -| React | 前端 UI | -| Tailwind | 樣式系統 | -| Plotly/kaleido | 圖表渲染與輸出 | -| Playwright | 截圖與測試 | - diff --git a/TDD.md b/TDD.md deleted file mode 100644 index 944de81..0000000 --- a/TDD.md +++ /dev/null @@ -1,54 +0,0 @@ -# 📙 Test Driven Development (TDD) - -## 1. 測試分類與範圍 -| 類型 | 工具 | 範圍 | -|------|------|------| -| 單元測試 | pytest | importer、renderer、export 模組 | -| 端對端測試 | Playwright | 前端互動與整體流程 | -| 效能測試 | pytest-benchmark | 渲染與輸出效能 | - ---- - -## 2. 單元測試案例 -| 編號 | 測試項目 | 驗證重點 | -|------|-----------|------------| -| UT-IMP-01 | 匯入 CSV 欄位解析 | 欄位自動對應與格式容錯 | -| UT-REN-01 | 時間刻度演算法 | 不同時間跨度下刻度精準性 | -| UT-REN-02 | 節點避碰演算法 | 重疊節點之排版與間距合理性 | -| UT-EXP-01 | PDF 輸出完整性 | 字型嵌入與 DPI 驗證 | - ---- - -## 3. 端對端測試(E2E)流程 -1. 匯入測試資料(CSV)。 -2. 驗證時間軸正確渲染。 -3. 切換主題並重新渲染。 -4. 匯出 PNG/PDF 並確認檔案存在與開啟性。 -5. 驗證畫面快照差異 ≤ 0.5%。 - ---- - -## 4. 效能與穩定性測試 -| 測試項目 | 標準 | 通過條件 | -|-----------|------|-----------| -| 100 筆事件 | <1 秒 | 無延遲或崩潰 | -| 300 筆事件 | <3 秒 | FPS ≥ 30 | -| 匯出任務 | <2 秒 | 正確生成檔案 | - ---- - -## 5. 測試環境與自動化 -| 組件 | 工具 | -|------|------| -| 測試框架 | pytest, Playwright | -| 持續整合 | GitHub Actions | -| 覆蓋率 | coverage.py + htmlcov | -| 報告生成 | Allure / pytest-html | - ---- - -## 6. 驗收條件 -- 單元測試覆蓋率 ≥ 80%。 -- E2E 測試通過率 = 100%。 -- 效能達標:渲染與輸出均在 KPI 內。 - diff --git a/docs/FRONTEND_DEVELOPMENT.md b/docs/FRONTEND_DEVELOPMENT.md deleted file mode 100644 index 07c3188..0000000 --- a/docs/FRONTEND_DEVELOPMENT.md +++ /dev/null @@ -1,510 +0,0 @@ -# TimeLine Designer - 前端開發完成報告 - -**版本**: 1.0.0 -**日期**: 2025-11-05 -**技術棧**: React 18 + TypeScript + Vite + Tailwind CSS v3 -**DocID**: FRONTEND-DEV-001 - ---- - -## 🎯 開發摘要 - -### 完成項目 -- ✅ **React + Vite + TypeScript** 專案建立 -- ✅ **Tailwind CSS v3** 樣式系統配置 -- ✅ **完整 UI 元件** 實作 -- ✅ **API 客戶端** 整合 -- ✅ **Plotly.js** 時間軸圖表渲染 -- ✅ **檔案拖放上傳** 功能 -- ✅ **前端編譯通過** 準備就緒 - ---- - -## 📁 專案結構 - -``` -frontend-react/ -├── src/ -│ ├── api/ -│ │ ├── client.ts # Axios 客戶端配置 -│ │ └── timeline.ts # Timeline API 服務 -│ ├── types/ -│ │ └── index.ts # TypeScript 類型定義 -│ ├── App.tsx # 主要應用程式元件 -│ ├── index.css # Tailwind CSS 配置 -│ └── main.tsx # 應用入口 -├── .env.development # 開發環境變數 -├── tailwind.config.js # Tailwind 配置 -├── postcss.config.js # PostCSS 配置 -├── vite.config.ts # Vite 配置 -├── tsconfig.json # TypeScript 配置 -└── package.json # 專案依賴 -``` - ---- - -## 🛠️ 技術棧詳情 - -### 核心依賴 -```json -{ - "react": "^18.3.1", - "react-dom": "^18.3.1", - "typescript": "~5.6.2", - "vite": "^7.1.12" -} -``` - -### UI 依賴 -```json -{ - "tailwindcss": "^3.4.17", - "lucide-react": "^0.468.0", - "react-dropzone": "^14.3.5" -} -``` - -### 圖表與網路 -```json -{ - "plotly.js": "^2.36.0", - "react-plotly.js": "^2.6.0", - "axios": "^1.7.9" -} -``` - ---- - -## 🎨 UI 功能實作 - -### 1. 檔案上傳區 (File Upload) -**功能**: -- 拖放上傳 CSV/XLSX 檔案 -- 點擊上傳檔案 -- 即時視覺回饋 -- 支援檔案格式驗證 - -**技術**: -- `react-dropzone` 處理拖放 -- FormData API 上傳 -- MIME 類型驗證 - -**程式碼位置**: `App.tsx:60-68` - ---- - -### 2. 事件管理 -**功能**: -- 顯示目前事件數量 -- 生成時間軸按鈕 -- 清空所有事件 - -**狀態管理**: -```typescript -const [eventsCount, setEventsCount] = useState(0); -``` - -**API 整合**: -- GET `/api/events` - 取得事件列表 -- DELETE `/api/events` - 清空事件 - ---- - -### 3. 時間軸預覽 (Timeline Preview) -**功能**: -- Plotly.js 互動式圖表 -- 響應式容器 (100% 寬度, 600px 高度) -- 載入動畫 -- 空狀態提示 - -**Plotly 整合**: -```typescript - -``` - -**程式碼位置**: `App.tsx:230-244` - ---- - -### 4. 匯出選項 (Export Options) -**功能**: -- 格式選擇 (PDF, PNG, SVG) -- DPI 設定 (150, 300, 600) -- 自動下載檔案 - -**實作**: -```typescript -const exportTimeline = async () => { - const blob = await timelineAPI.exportTimeline(plotlyData, plotlyLayout, options); - const url = window.URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `timeline.${exportFormat}`; - a.click(); - window.URL.revokeObjectURL(url); -}; -``` - ---- - -## 🔌 API 客戶端架構 - -### API Base Configuration -```typescript -// src/api/client.ts -const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:12010/api'; - -export const apiClient = axios.create({ - baseURL: API_BASE_URL, - headers: { 'Content-Type': 'application/json' }, - timeout: 30000, -}); -``` - -### API 服務層 -```typescript -// src/api/timeline.ts -export const timelineAPI = { - async importFile(file: File): Promise { ... }, - async getEvents(): Promise { ... }, - async renderTimeline(config?: TimelineConfig): Promise { ... }, - async exportTimeline(...): Promise { ... }, - // ...更多 API -}; -``` - -**優勢**: -- 類型安全 (TypeScript) -- 統一錯誤處理 -- Request/Response 攔截器 -- 易於測試和維護 - ---- - -## ⚙️ Vite 配置 - -### 開發伺服器 -```typescript -// vite.config.ts -export default defineConfig({ - server: { - port: 12010, - proxy: { - '/api': { - target: 'http://localhost:8000', - changeOrigin: true, - }, - }, - }, -}); -``` - -**說明**: -- 前端端口: `12010` -- 後端代理: `/api` → `http://localhost:8000` -- 符合 CLAUDE.md 端口規範 (12010-12019) - ---- - -## 🎨 Tailwind CSS 配置 - -### 自訂主題色 -```javascript -// tailwind.config.js -theme: { - extend: { - colors: { - primary: { - 500: '#667eea', // 主要漸層起點 - 600: '#5b68e0', - }, - secondary: { - 500: '#764ba2', // 主要漸層終點 - 600: '#6b4391', - }, - }, - }, -} -``` - -### 自訂 CSS 類別 -```css -/* src/index.css */ -.btn-primary { - @apply btn bg-gradient-to-r from-primary-500 to-secondary-500 text-white; -} - -.card { - @apply bg-white rounded-xl shadow-2xl p-6; -} - -.section-title { - @apply text-2xl font-bold text-primary-600 mb-4 pb-2 border-b-2; -} -``` - ---- - -## 📝 TypeScript 類型系統 - -### Event 類型 -```typescript -export interface Event { - id: string; - title: string; - start: string; // ISO date string - end?: string; - group?: string; - description?: string; - color?: string; -} -``` - -### API Response 類型 -```typescript -export interface APIResponse { - success: boolean; - message?: string; - data?: T; - error_code?: string; -} - -export interface ImportResult { - success: boolean; - imported_count: number; - events: Event[]; - errors: string[]; -} -``` - -**優勢**: -- 編譯時類型檢查 -- IDE 自動完成 -- 重構安全 -- 減少執行時錯誤 - ---- - -## 🚀 啟動方式 - -### 開發環境 - -#### 方法 1: 使用便捷腳本 -```batch -# Windows -start_dev.bat -``` - -這會同時啟動: -- 後端: `http://localhost:8000` -- 前端: `http://localhost:12010` - -#### 方法 2: 手動啟動 - -**後端**: -```bash -conda activate timeline_designer -uvicorn backend.main:app --reload --port 8000 -``` - -**前端**: -```bash -cd frontend-react -npm run dev -``` - -### 生產環境 - -**建置前端**: -```bash -cd frontend-react -npm run build -``` - -**輸出**: -- 目錄: `frontend-react/dist/` -- 檔案大小: ~5.2 MB (含 Plotly.js) -- Gzip 壓縮: ~1.6 MB - ---- - -## 🔍 技術亮點 - -### 1. 響應式設計 -- Tailwind CSS utility classes -- Flexbox 佈局 -- 響應式容器 (`max-w-7xl mx-auto`) - -### 2. 使用者體驗 -- 拖放上傳檔案 -- 即時載入狀態 -- 自動消失的訊息提示 (5 秒) -- 按鈕禁用狀態管理 - -### 3. 效能優化 -- `useCallback` 避免重複渲染 -- Vite 快速熱更新 (HMR) -- 生產環境 Tree Shaking - -### 4. 程式碼品質 -- TypeScript 嚴格模式 -- ESLint 程式碼檢查 -- 統一的 API 層 -- 清晰的檔案組織 - ---- - -## 📊 編譯結果 - -### 成功編譯 -``` -✓ 1748 modules transformed -✓ built in 31.80s - -dist/index.html 0.46 kB │ gzip: 0.29 kB -dist/assets/index-v0MVqyGF.css 13.54 kB │ gzip: 3.14 kB -dist/assets/index-RyjrDfo0.js 5,185.45 kB │ gzip: 1,579.67 kB -``` - -### Bundle 分析 -- **Total Size**: ~5.2 MB -- **Gzip Size**: ~1.6 MB -- **主要貢獻者**: Plotly.js (~4 MB) - -**優化建議** (可選): -- Dynamic import Plotly -- 使用 plotly.js-basic-dist (更小的版本) -- Code splitting - ---- - -## 🧪 測試建議 - -### 手動測試清單 -- [ ] 上傳 CSV 檔案 -- [ ] 檢視事件數量 -- [ ] 生成時間軸 -- [ ] 互動式縮放、拖曳 -- [ ] 匯出 PDF/PNG/SVG -- [ ] 清空事件 -- [ ] 錯誤處理 (無效檔案) - -### 未來測試 -- Jest + React Testing Library -- E2E 測試 (Playwright) -- 視覺回歸測試 - ---- - -## 📋 環境配置 - -### .env.development -```env -VITE_API_BASE_URL=http://localhost:12010/api -``` - -### 環境變數使用 -```typescript -const API_BASE_URL = import.meta.env.VITE_API_BASE_URL; -``` - ---- - -## 🐛 已知問題與解決方案 - -### Issue 1: Tailwind CSS v4 不相容 -**問題**: v4 使用新的 PostCSS 插件 -**解決**: 降級至 v3.4.17 (穩定版本) - -### Issue 2: Plotly.js Bundle Size -**問題**: Bundle 超過 5MB -**狀態**: 可接受 (地端運行,無頻寬限制) -**未來**: 考慮使用 plotly.js-basic-dist - -### Issue 3: TypeScript 類型警告 -**問題**: `react-plotly.js` 類型缺失 -**解決**: 安裝 `@types/react-plotly.js` - ---- - -## 🎯 PRD 需求對應 - -| PRD 需求 | 實作狀態 | 說明 | -|---------|---------|------| -| 零程式門檻 GUI | ✅ | 拖放上傳,一鍵生成 | -| React + Tailwind | ✅ | 完整實作 | -| 即時預覽 | ✅ | Plotly 互動式圖表 | -| 高解析輸出 | ✅ | DPI 150/300/600 選項 | -| 拖曳縮放 | ✅ | Plotly 內建支援 | -| 主題切換 | 🔶 | 後端已實作,前端待新增 UI | -| 快速渲染 | ✅ | < 2 秒 (100 筆事件) | - ---- - -## 📁 交付清單 - -### 程式碼檔案 -- ✅ `frontend-react/src/App.tsx` - 主應用 -- ✅ `frontend-react/src/api/client.ts` - API 客戶端 -- ✅ `frontend-react/src/api/timeline.ts` - API 服務 -- ✅ `frontend-react/src/types/index.ts` - 類型定義 -- ✅ `frontend-react/src/index.css` - 樣式配置 -- ✅ `frontend-react/vite.config.ts` - Vite 配置 -- ✅ `frontend-react/tailwind.config.js` - Tailwind 配置 - -### 配置檔案 -- ✅ `.env.development` - 環境變數 -- ✅ `package.json` - 專案依賴 -- ✅ `tsconfig.json` - TypeScript 配置 - -### 啟動腳本 -- ✅ `start_dev.bat` - Windows 開發環境啟動 - -### 文檔 -- ✅ `docs/FRONTEND_DEVELOPMENT.md` - 本文檔 - ---- - -## 🏆 最終評價 - -### 優勢 -1. ✅ **現代化技術棧** - React 18 + Vite + TypeScript -2. ✅ **類型安全** - 完整 TypeScript 支援 -3. ✅ **響應式 UI** - Tailwind CSS -4. ✅ **互動式圖表** - Plotly.js 整合 -5. ✅ **良好架構** - API 層分離,易於維護 - -### 特色 -- 🎨 **美觀設計** - 漸層背景,卡片式佈局 -- 🚀 **快速開發** - Vite HMR,開發效率高 -- 📦 **一鍵啟動** - start_dev.bat 腳本 -- 🔌 **API 整合** - 完整對接後端 9 個端點 - -### 改進空間 -1. ⚠️ **Bundle 大小** - 可優化 Plotly.js 引入方式 -2. ⚠️ **測試覆蓋** - 需新增單元測試 -3. ⚠️ **主題切換 UI** - 待實作前端控制 - -### 結論 -**TimeLine Designer 前端開發完成,功能完整,準備投入使用。** - -前端使用 React + TypeScript + Tailwind CSS 現代化技術棧,提供直覺的拖放上傳、即時預覽、高品質匯出等功能。與 FastAPI 後端完美整合,符合 PRD.md 所有核心需求。 - -建議後續工作:新增單元測試、實作主題切換 UI、優化 Bundle 大小。 - ---- - -**報告製作**: Claude Code -**最後更新**: 2025-11-05 16:40 -**文件版本**: 1.0.0 (Frontend Complete) -**變更**: React 前端完整實作 + 編譯通過 - -**相關文件**: -- PRD.md - 產品需求文件 -- INTEGRATION_TEST_REPORT.md - 整合測試報告 -- TDD.md - 技術設計文件 diff --git a/docs/INTEGRATION_TEST_REPORT.md b/docs/INTEGRATION_TEST_REPORT.md deleted file mode 100644 index 9863f01..0000000 --- a/docs/INTEGRATION_TEST_REPORT.md +++ /dev/null @@ -1,567 +0,0 @@ -# TimeLine Designer - 整合測試報告 - -**版本**: 1.0.0 -**日期**: 2025-11-05 -**測試環境**: Windows + Python 3.10.19 (Conda) -**DocID**: TEST-REPORT-003 (Integration Tests) -**相關文件**: TEST_REPORT_FINAL.md, TDD.md - ---- - -## 🎯 執行摘要 - -### 整合測試成果 -- ✅ **21 個整合測試全部通過** (100% 通過率) -- 🚀 **總覆蓋率提升至 75%** (從 66% +9%) -- 🎯 **main.py 覆蓋率達 82%** (從 40% +42%) -- ⏱️ **執行時間**: 6.02 秒 - -### 主要成就 -1. ✅ 完整測試所有 9 個 FastAPI 端點 -2. ✅ 修復 3 個測試失敗案例 -3. ✅ 建立可重用的測試基礎架構 -4. ✅ 達成 80%+ API 層覆蓋率目標 - ---- - -## 📋 測試範圍 - -### API 端點覆蓋 (9/9 完整) - -| 端點 | 方法 | 測試數量 | 狀態 | -|------|------|---------|------| -| `/health` | GET | 1 | ✅ | -| `/api/import` | POST | 3 | ✅ | -| `/api/events` | GET, POST, DELETE | 6 | ✅ | -| `/api/render` | POST | 4 | ✅ | -| `/api/export` | POST | 3 | ✅ | -| `/api/themes` | GET | 2 | ✅ | -| **Workflows** | - | 2 | ✅ | -| **總計** | - | **21** | **✅ 100%** | - ---- - -## 📊 詳細測試清單 - -### 1. 健康檢查 API (1 test) -- ✅ `test_health_check_success` - 驗證服務健康狀態 - - 確認返回 200 OK - - 驗證版本資訊存在 - -### 2. 匯入 API (3 tests) -- ✅ `test_import_csv_success` - CSV 檔案匯入成功 - - 驗證事件資料正確解析 - - 確認匯入數量正確 - -- ✅ `test_import_invalid_file_type` - 無效檔案類型處理 - - 上傳 .txt 檔案 - - 預期返回 400 + 錯誤訊息 - -- ✅ `test_import_no_filename` - 空檔名驗證 - - 預期返回 422 (FastAPI 驗證錯誤) - -### 3. 事件管理 API (6 tests) -- ✅ `test_get_events_empty` - 取得空事件列表 -- ✅ `test_add_event_success` - 新增事件成功 - - 驗證事件資料正確儲存 - -- ✅ `test_add_event_invalid_date` - 無效日期驗證 - - 結束日期早於開始日期 - - 預期返回 422 - -- ✅ `test_get_events_after_add` - 新增後查詢驗證 -- ✅ `test_delete_event_success` - 刪除事件成功 -- ✅ `test_delete_nonexistent_event` - 刪除不存在的事件 - - 預期返回 404 - - 使用 APIResponse 格式 - -### 4. 渲染 API (4 tests) -- ✅ `test_render_basic` - 基本時間軸渲染 - - 驗證 Plotly JSON 格式 - -- ✅ `test_render_with_config` - 自訂配置渲染 - - horizontal 方向 - - classic 主題 - -- ✅ `test_render_empty_timeline` - 空時間軸渲染 -- ✅ `test_render_with_groups` - 群組渲染 - - 多個不同群組的事件 - -### 5. 匯出 API (3 tests) -- ✅ `test_export_pdf` - PDF 匯出 - - 驗證檔案格式正確 - -- ✅ `test_export_png` - PNG 匯出 - - DPI 300 設定 - -- ✅ `test_export_svg` - SVG 匯出 - - 向量格式驗證 - -### 6. 主題 API (2 tests) -- ✅ `test_get_themes_list` - 取得主題列表 - - 至少包含 modern, classic, dark - -- ✅ `test_themes_format` - 主題格式驗證 - - 驗證資料結構正確 - -### 7. 完整工作流程 (2 tests) -- ✅ `test_full_workflow_csv_to_pdf` - CSV → PDF 完整流程 - 1. 匯入 CSV - 2. 取得事件列表 - 3. 渲染時間軸 - 4. 匯出 PDF - -- ✅ `test_full_workflow_manual_events` - 手動建立事件流程 - 1. 新增多個事件 - 2. 渲染為圖表 - 3. 匯出為 PNG - ---- - -## 🔧 測試修復記錄 - -### 問題 1: AsyncClient 初始化錯誤 -**症狀**: `TypeError: AsyncClient.__init__() got an unexpected keyword argument 'app'` - -**原因**: httpx 0.28.1 API 變更,不再接受 `app=` 參數 - -**解決方案**: -```python -# 修復前 -async with AsyncClient(app=app, base_url="http://test") as ac: - yield ac - -# 修復後 -from httpx import AsyncClient, ASGITransport -transport = ASGITransport(app=app) -async with AsyncClient(transport=transport, base_url="http://test") as ac: - yield ac -``` - -**檔案**: `tests/integration/conftest.py:19` - ---- - -### 問題 2: 匯入驗證錯誤被捕獲為 500 -**症狀**: -- `test_import_invalid_file_type` 期望 400,實際返回 500 -- 驗證錯誤被 Exception handler 捕獲 - -**原因**: HTTPException 被 `except Exception` 捕獲並轉換為 500 錯誤 - -**解決方案**: -```python -# backend/main.py:133-141 -except HTTPException: - # Re-raise HTTP exceptions (from validation) - raise -except ImporterError as e: - logger.error(f"匯入失敗: {str(e)}") - raise HTTPException(status_code=400, detail=str(e)) -except Exception as e: - logger.error(f"未預期的錯誤: {str(e)}") - raise HTTPException(status_code=500, detail=f"伺服器錯誤: {str(e)}") -``` - -**檔案**: `backend/main.py:133` - ---- - -### 問題 3: 刪除測試回應格式不匹配 -**症狀**: -- `test_delete_nonexistent_event` 期望 `response.json()["detail"]` -- 實際返回 `KeyError: 'detail'` - -**原因**: 自訂 404 exception handler 使用 APIResponse 格式 - -**解決方案**: -```python -# 更新測試以匹配 API 實際行為 -assert response.status_code == 404 -data = response.json() -assert data["success"] is False -assert "找不到" in data["message"] or data["error_code"] == "NOT_FOUND" -``` - -**檔案**: `tests/integration/test_api.py:207-211` - ---- - -### 問題 4: 空檔名驗證狀態碼 -**症狀**: `test_import_no_filename` 期望 400,實際返回 422 - -**原因**: FastAPI 在請求處理早期進行驗證,返回 422 (Unprocessable Entity) - -**解決方案**: -```python -# 更新測試以接受 FastAPI 的標準驗證狀態碼 -assert response.status_code in [400, 422] -``` - -**說明**: 422 是 FastAPI 的標準驗證錯誤狀態碼,語意上比 400 更精確 - -**檔案**: `tests/integration/test_api.py:93` - ---- - -## 📈 覆蓋率分析 - -### 覆蓋率對比 - -| 模組 | 單元測試後 | 整合測試後 | 提升 | 評級 | -|------|----------|----------|------|------| -| **main.py** | 40% | **82%** | **+42%** | A | -| export.py | 84% | 76% | -8% | A | -| importer.py | 77% | 66% | -11% | B+ | -| renderer.py | 83% | 67% | -16% | B+ | -| schemas.py | 100% | 99% | -1% | A+ | -| **總計** | **66%** | **75%** | **+9%** | **A-** | - -### 覆蓋率提升說明 - -**main.py 大幅提升** (40% → 82%): -- 整合測試覆蓋所有 API 端點 -- 測試完整請求處理流程 -- 驗證錯誤處理機制 - -**其他模組覆蓋率降低原因**: -- 單獨執行整合測試時,僅觸發 main.py 呼叫的路徑 -- 某些單元測試覆蓋的邊界情況未被整合測試觸發 -- 這是正常現象,兩種測試類型互補 - -**組合覆蓋率**: -- 單元測試 (77 tests) + 整合測試 (21 tests) = **98 tests** -- 預估組合覆蓋率: **80%+** - -### 未覆蓋代碼分析 - -#### main.py (22 statements, 82% coverage) -**未覆蓋原因**: -1. Line 102: 空檔名檢查 (FastAPI 提前驗證) -2. Lines 136-141: HTTPException 重新拋出路徑 -3. Lines 248, 252-254: 特定錯誤處理情境 -4. Lines 311-312, 329-334: Render/Export 錯誤處理 -5. Lines 400-401, 416-417, 423, 427-428: 啟動/關閉事件處理 - -**改進建議**: 新增錯誤情境測試 - ---- - -## 🏗️ 測試基礎架構 - -### 目錄結構 -``` -tests/ -├── unit/ # 單元測試 (77 tests) -│ ├── test_schemas.py -│ ├── test_importer.py -│ ├── test_renderer.py -│ └── test_export.py -└── integration/ # 整合測試 (21 tests) ⭐ NEW - ├── __init__.py - ├── conftest.py # 測試配置 - └── test_api.py # API 端點測試 -``` - -### Fixtures - -#### `client` - AsyncClient Fixture -```python -@pytest_asyncio.fixture -async def client(): - """AsyncClient for testing FastAPI endpoints""" - transport = ASGITransport(app=app) - async with AsyncClient(transport=transport, base_url="http://test") as ac: - yield ac -``` - -**用途**: 提供 async HTTP client 測試 FastAPI 端點 - -#### `sample_csv_content` - 範例 CSV Fixture -```python -@pytest.fixture -def sample_csv_content(): - """範例 CSV 內容""" - return b"""id,title,start,end,group,description,color -evt-001,Event 1,2024-01-01,2024-01-02,Group A,Test event 1,#3B82F6 -evt-002,Event 2,2024-01-05,2024-01-06,Group B,Test event 2,#10B981 -evt-003,Event 3,2024-01-10,,Group A,Test event 3,#F59E0B -""" -``` - -**用途**: 提供一致的測試資料 - ---- - -## 🚀 執行方式 - -### 執行所有整合測試 -```bash -# 使用 Conda 環境 -conda activate timeline_designer -pytest tests/integration/ -v - -# 包含覆蓋率報告 -pytest tests/integration/ -v --cov=backend --cov-report=html -``` - -### 執行特定測試類別 -```bash -# 僅測試匯入 API -pytest tests/integration/test_api.py::TestImportAPI -v - -# 僅測試事件管理 API -pytest tests/integration/test_api.py::TestEventsAPI -v -``` - -### 執行特定測試 -```bash -pytest tests/integration/test_api.py::TestWorkflows::test_full_workflow_csv_to_pdf -v -``` - -### 查看覆蓋率報告 -```bash -# 執行測試並生成 HTML 報告 -pytest tests/integration/ --cov=backend --cov-report=html:docs/validation/coverage/htmlcov - -# 開啟 HTML 報告 -start docs/validation/coverage/htmlcov/index.html -``` - ---- - -## 📝 測試最佳實踐 - -### 1. 測試獨立性 -- ✅ 每個測試獨立運行 -- ✅ 使用 fixture 提供乾淨的測試環境 -- ✅ 不依賴測試執行順序 - -### 2. 明確的測試意圖 -- ✅ 測試名稱清楚描述測試目的 -- ✅ 使用 docstring 說明測試情境 -- ✅ 對應 TDD.md 中的測試案例編號 - -### 3. 完整的驗證 -- ✅ 驗證 HTTP 狀態碼 -- ✅ 驗證回應資料結構 -- ✅ 驗證業務邏輯正確性 - -### 4. 錯誤處理測試 -- ✅ 測試正常流程 -- ✅ 測試錯誤情境 -- ✅ 驗證錯誤訊息準確性 - ---- - -## 🎯 測試覆蓋完整性 - -### API 端點覆蓋 - 100% -| 端點 | 正常情境 | 錯誤情境 | 邊界情況 | 評級 | -|------|---------|---------|---------|------| -| Health Check | ✅ | - | - | A+ | -| Import CSV | ✅ | ✅ | ✅ | A+ | -| Events CRUD | ✅ | ✅ | ✅ | A+ | -| Render | ✅ | ✅ | ✅ | A+ | -| Export | ✅ | - | ✅ | A | -| Themes | ✅ | - | - | A | -| **總體評級** | **100%** | **67%** | **67%** | **A** | - -### 測試類型分布 -- **功能測試**: 15 tests (71%) -- **錯誤處理**: 4 tests (19%) -- **整合流程**: 2 tests (10%) - ---- - -## 📊 效能指標 - -### 測試執行時間 -- **總執行時間**: 6.02 秒 -- **平均每測試**: 0.29 秒 -- **最慢測試**: ~0.5 秒 (匯出相關測試) -- **最快測試**: ~0.1 秒 (簡單 GET 請求) - -### 效能評級 -- ⚡ **優秀** (< 10 秒): ✅ 達成 -- 🟢 **良好** (< 30 秒): ✅ 達成 -- 🟡 **可接受** (< 60 秒): ✅ 達成 - ---- - -## ✅ 驗收標準達成度 - -| 標準 | 要求 | 實際 | 達成 | 備註 | -|------|------|------|------|------| -| 整合測試通過率 | 100% | 100% | ✅ | 21/21 通過 | -| API 端點覆蓋 | 100% | 100% | ✅ | 9/9 端點 | -| main.py 覆蓋率 | ≥ 80% | 82% | ✅ | 超越目標 | -| 總覆蓋率提升 | ≥ +5% | +9% | ✅ | 超越目標 | -| 執行時間 | < 30 秒 | 6.02 秒 | ✅ | 遠低於標準 | -| 錯誤情境測試 | ≥ 50% | 67% | ✅ | 超越目標 | -| **總體評價** | **優秀** | **優秀** | **✅** | **全面達標** | - ---- - -## 🎖️ 重大成就 - -### 1. ✅ 100% 整合測試通過率 -- 21 個測試全部通過 -- 涵蓋所有 9 個 API 端點 -- 包含正常流程與錯誤處理 - -### 2. ✅ main.py 覆蓋率突破 80% -- 從 40% 提升至 82% -- +42% 顯著提升 -- 達成 TDD 目標 - -### 3. ✅ 總覆蓋率達 75% -- 從 66% 提升至 75% -- +9% 整體提升 -- 核心模組均達 66%+ - -### 4. ✅ 建立完整測試基礎架構 -- AsyncClient 測試配置 -- 可重用 fixtures -- 清晰的測試組織 - -### 5. ✅ 修復所有測試失敗 -- 3 個失敗案例全部解決 -- 根本原因分析完整 -- 解決方案文檔完善 - ---- - -## 🔄 與單元測試對比 - -### 互補性分析 - -**單元測試優勢**: -- 細粒度測試 -- 快速執行 -- 易於定位問題 -- 覆蓋邊界情況 - -**整合測試優勢**: -- 端到端驗證 -- 真實場景模擬 -- API 合約驗證 -- 系統整合確認 - -**組合效果**: -- 單元測試: 77 tests, 66% coverage -- 整合測試: 21 tests, 75% coverage -- **組合覆蓋率預估: 80%+** - ---- - -## 📋 後續建議 - -### 優先級 1 - 高 (建議完成) -1. **新增錯誤情境測試** - - 磁碟空間不足 - - 網路逾時 - - 大檔案處理 - -2. **擴充邊界測試** - - 極大事件數量 (1000+) - - 極長檔名 - - 特殊字元處理 - -### 優先級 2 - 中 (可選完成) -3. **效能測試** - - 並發請求測試 - - 大量資料匯入 - - 記憶體使用分析 - -4. **安全性測試** - - SQL 注入防禦 - - XSS 防禦 - - 檔案上傳驗證 - -### 優先級 3 - 低 (未來改進) -5. **E2E 測試** - - Playwright 前端測試 - - 完整使用者流程 - -6. **負載測試** - - Apache Bench - - Locust 壓力測試 - ---- - -## 🔍 技術細節 - -### 依賴版本 -``` -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -httpx==0.28.1 -fastapi==0.104.1 -``` - -### 測試配置 (pytest.ini) -```ini -[tool:pytest] -asyncio_mode = strict -testpaths = tests -python_files = test_*.py -python_classes = Test* -python_functions = test_* -``` - ---- - -## 📦 交付清單 - -### 測試檔案 -- ✅ `tests/integration/__init__.py` -- ✅ `tests/integration/conftest.py` -- ✅ `tests/integration/test_api.py` - -### 程式碼修改 -- ✅ `backend/main.py` (HTTPException 處理修復) -- ✅ `tests/integration/test_api.py` (測試修正) - -### 文檔 -- ✅ `docs/INTEGRATION_TEST_REPORT.md` (本報告) -- ✅ 覆蓋率 HTML 報告: `docs/validation/coverage/htmlcov/` - -### 輔助工具 -- ✅ `run_integration_tests.bat` (Windows 批次腳本) - ---- - -## 🏆 最終評價 - -### 優勢 -1. ✅ **100% API 端點覆蓋** - 完整驗證 -2. ✅ **82% main.py 覆蓋率** - 超越目標 -3. ✅ **6 秒快速執行** - 效能優異 -4. ✅ **21 個測試全通過** - 品質保證 -5. ✅ **完整錯誤處理** - 穩健性高 - -### 限制 -1. ⚠️ **部分錯誤情境未覆蓋** - 需補充測試 -2. ⚠️ **效能測試缺失** - 未測試高負載 -3. ⚠️ **安全性測試不足** - 需專項測試 - -### 結論 -**TimeLine Designer API 層已充分驗證,品質優秀,可進入下一開發階段。** - -整合測試成功填補了單元測試的空缺,main.py 覆蓋率從 40% 提升至 82%,總覆蓋率達 75%。所有核心 API 功能經過完整測試,錯誤處理機制運作正常,系統穩定性得到保證。 - -建議優先實作錯誤情境與效能測試,進一步提升系統品質。 - ---- - -**報告製作**: Claude Code -**最後更新**: 2025-11-05 16:10 -**文件版本**: 1.0.0 (Integration Tests Complete) -**變更**: 新增整合測試 + API 層覆蓋率達 82% - -**相關報告**: -- TEST_REPORT_FINAL.md - 單元測試報告 -- TDD.md - 技術設計文件 -- SDD.md - 系統設計文件 diff --git a/docs/TEST_REPORT.md b/docs/TEST_REPORT.md deleted file mode 100644 index 0811236..0000000 --- a/docs/TEST_REPORT.md +++ /dev/null @@ -1,349 +0,0 @@ -# TimeLine Designer - 測試報告 - -**版本**: 1.0.0 -**日期**: 2025-11-05 -**測試環境**: Windows + Python 3.10.19 (Conda) -**DocID**: TEST-REPORT-001 - ---- - -## 📊 測試結果總覽 - -### 測試統計 -- ✅ **通過測試**: 60/60 (100%) -- ⏭️ **跳過測試**: 17 (Kaleido 相關) -- ❌ **失敗測試**: 0 -- **總執行時間**: 0.99 秒 - -### 測試覆蓋率 -``` -Module Coverage Tested Lines Missing Lines -================================================================ -backend/__init__.py 100% 4/4 0 -backend/schemas.py 100% 81/81 0 -backend/renderer.py 83% 154/186 32 -backend/importer.py 77% 119/154 35 -backend/export.py 49% 49/100 51 -backend/main.py 0% 0/142 142 -================================================================ -總計 61% 407/667 260 -``` - ---- - -## ✅ 測試模組詳細報告 - -### 1. schemas.py - 資料模型測試 -**覆蓋率**: 100% ✓ - -**測試項目** (9項): -- ✅ test_create_valid_event - 測試建立有效事件 -- ✅ test_event_end_before_start_validation - 測試時間驗證 -- ✅ test_event_with_invalid_color - 測試顏色格式驗證 -- ✅ test_event_optional_fields - 測試可選欄位 -- ✅ test_default_config - 測試預設配置 -- ✅ test_custom_config - 測試自訂配置 -- ✅ test_valid_export_options - 測試匯出選項 -- ✅ test_dpi_range_validation - 測試 DPI 範圍驗證 -- ✅ test_dimension_validation - 測試尺寸驗證 - -**結論**: 所有 Pydantic 資料模型驗證功能正常運作。 - ---- - -### 2. importer.py - CSV/XLSX 匯入模組 -**覆蓋率**: 77% ✓ - -**測試項目** (19項): -- ✅ test_map_english_fields - 測試英文欄位映射 -- ✅ test_map_chinese_fields - 測試中文欄位映射 -- ✅ test_validate_missing_fields - 測試缺少必要欄位驗證 -- ✅ test_parse_standard_format - 測試標準日期格式 -- ✅ test_parse_date_only - 測試僅日期格式 -- ✅ test_parse_slash_format - 測試斜線格式 -- ✅ test_parse_invalid_date - 測試無效日期 -- ✅ test_parse_empty_string - 測試空字串 -- ✅ test_validate_valid_hex - 測試有效 HEX 顏色 -- ✅ test_validate_hex_without_hash - 測試不含 # 的 HEX -- ✅ test_validate_invalid_color - 測試無效顏色 -- ✅ test_validate_empty_color - 測試空顏色 -- ✅ test_import_valid_csv - 測試匯入有效 CSV -- ✅ test_import_with_invalid_dates - 測試日期格式錯誤 -- ✅ test_import_nonexistent_file - 測試不存在檔案 -- ✅ test_field_auto_mapping - 測試欄位自動對應 -- ✅ test_color_format_validation - 測試顏色格式驗證 -- ✅ test_import_empty_csv - 測試空白 CSV -- ✅ test_date_format_tolerance - 測試日期格式容錯 - -**未覆蓋部分** (35 statements): -- XLSX 匯入器 (未實作) -- 部分錯誤處理邊界情況 - -**結論**: CSV 匯入核心功能完整測試,支援多種日期格式與欄位映射。 - ---- - -### 3. renderer.py - 時間軸渲染模組 -**覆蓋率**: 83% ✓ - -**測試項目** (20項): -- ✅ test_calculate_time_range - 測試時間範圍計算 -- ✅ test_determine_time_unit_days - 測試天級別刻度判斷 -- ✅ test_determine_time_unit_weeks - 測試週級別刻度判斷 -- ✅ test_determine_time_unit_months - 測試月級別刻度判斷 -- ✅ test_generate_tick_values_days - 測試天級別刻度生成 -- ✅ test_generate_tick_values_months - 測試月級別刻度生成 -- ✅ test_no_overlapping_events - 測試無重疊事件 -- ✅ test_overlapping_events - 測試重疊事件分層 -- ✅ test_group_based_layout - 測試基於群組的排版 -- ✅ test_empty_events - 測試空事件列表 -- ✅ test_get_modern_theme - 測試現代主題 -- ✅ test_get_all_themes - 測試所有主題可用性 -- ✅ test_render_basic_timeline - 測試基本時間軸渲染 -- ✅ test_render_empty_timeline - 測試空白時間軸渲染 -- ✅ test_render_with_horizontal_direction - 測試水平方向渲染 -- ✅ test_render_with_vertical_direction - 測試垂直方向渲染 -- ✅ test_render_with_different_themes - 測試不同主題渲染 -- ✅ test_render_with_grid - 測試顯示網格 -- ✅ test_render_single_event - 測試單一事件渲染 -- ✅ test_hover_text_generation - 測試提示訊息生成 - -**未覆蓋部分** (32 statements): -- 年級別時間刻度處理 -- 部分主題配色邊界情況 -- 特殊事件類型渲染 - -**結論**: 時間軸渲染核心演算法(刻度計算、避碰、主題)功能完整。 - ---- - -### 4. export.py - 匯出模組 -**覆蓋率**: 49% - -**測試項目** (12項通過 + 17項跳過): - -**已執行測試**: -- ✅ test_sanitize_normal_name - 測試正常檔名 -- ✅ test_sanitize_illegal_chars - 測試移除非法字元 -- ✅ test_sanitize_reserved_name - 測試保留字處理 -- ✅ test_sanitize_long_name - 測試過長檔名 -- ✅ test_sanitize_empty_name - 測試空檔名 -- ✅ test_sanitize_trailing_spaces - 測試移除尾部空格 -- ✅ test_export_engine_initialization - 測試引擎初始化 -- ✅ test_exporter_initialization - 測試匯出器初始化 -- ✅ test_generate_default_filename - 測試預設檔名生成 -- ✅ test_generate_default_filename_format - 測試檔名格式 -- ✅ test_create_metadata_default - 測試預設元資料 -- ✅ test_create_metadata_custom_title - 測試自訂標題 - -**已跳過測試** (Kaleido 相關): -- ⏭️ test_export_pdf_basic -- ⏭️ test_export_png_basic -- ⏭️ test_export_svg_basic -- ⏭️ test_export_png_with_transparency -- ⏭️ test_export_custom_dimensions -- ⏭️ test_export_high_dpi -- ⏭️ test_export_creates_directory -- ⏭️ test_export_filename_sanitization -- ⏭️ test_export_from_plotly_json -- ⏭️ test_export_to_directory_with_default_name -- ⏭️ test_export_to_readonly_location -- ⏭️ test_export_empty_timeline -- ⏭️ test_pdf_file_format -- ⏭️ test_png_file_format -- ⏭️ test_svg_file_format -- ⏭️ test_full_workflow_pdf -- ⏭️ test_full_workflow_all_formats - -**結論**: -- 檔名處理、元資料生成等邏輯功能已驗證 ✓ -- 實際圖片生成功能因 Kaleido 在 Windows 環境的已知問題而暫時跳過 -- 在 Linux/Mac 環境或 Kaleido 修復後可完整測試 - ---- - -### 5. main.py - FastAPI 端點 -**覆蓋率**: 0% - -**說明**: -- main.py 包含 9 個 FastAPI REST API 端點 -- 這些端點需要透過**整合測試**或**E2E 測試**進行驗證 -- 單元測試階段不涵蓋 API 路由層 - -**API 端點列表**: -``` -GET /health - 健康檢查 -POST /api/import - 匯入 CSV/XLSX -GET /api/events - 取得事件列表 -POST /api/events - 新增事件 -PUT /api/events/{id} - 更新事件 -DELETE /api/events/{id} - 刪除事件 -POST /api/render - 渲染時間軸 -POST /api/export - 匯出圖檔 -GET /api/themes - 取得主題列表 -``` - ---- - -## 🔍 問題與限制 - -### 1. Kaleido 圖片生成問題 -**問題描述**: -Kaleido 0.2.1 在 Windows 環境中執行 `write_image()` 時會無限掛起,無法生成 PDF/PNG/SVG 圖檔。 - -**影響範圍**: -- export.py 模組中 17 個圖片生成相關測試 -- export.py 覆蓋率從預期 80%+ 降至 49% - -**解決方案**: -1. **短期**: 測試已標記 `@pytest.mark.skip`,不影響其他測試執行 -2. **中期**: 在 Linux/Mac 環境中執行完整測試 -3. **長期**: 等待 Kaleido 更新或考慮替代方案 (如 plotly-orca) - -### 2. API 端點未測試 -**問題描述**: -FastAPI 路由層需要整合測試,不在單元測試範圍內。 - -**影響範圍**: -- main.py 模組 0% 覆蓋率 -- 9 個 API 端點未經自動化測試 - -**解決方案**: -- 實作整合測試 (使用 pytest + httpx) -- 實作 E2E 測試 (使用 Playwright) - ---- - -## 📈 測試品質分析 - -### 優勢 -1. ✅ **核心業務邏輯覆蓋率高** - - schemas.py: 100% - - renderer.py: 83% - - importer.py: 77% - -2. ✅ **測試執行速度快** - - 60 個測試僅需 0.99 秒 - - 適合快速迭代開發 - -3. ✅ **測試品質良好** - - 100% 測試通過率 - - 無任何測試失敗 - - 測試案例涵蓋正常與異常情境 - -4. ✅ **遵循 TDD 規範** - - 所有測試對應 TDD.md 規格 - - 測試文件完整,包含 DocID 追溯 - -### 待改進 -1. ⚠️ **總體覆蓋率 61%** (目標 80%) - - 主因: main.py (0%) 和 export.py (49%) - -2. ⚠️ **缺少整合測試** - - FastAPI 端點未測試 - - 模組間整合情境未驗證 - -3. ⚠️ **部分邊界情況未覆蓋** - - 年級別時間刻度 - - XLSX 匯入器 - - 特殊事件類型 - ---- - -## 🎯 後續建議 - -### 優先級 1 - 高 (必須完成) -1. **解決 Kaleido 問題** - - 在 Linux 環境中執行完整測試 - - 或升級/替換 Kaleido 依賴 - -2. **新增 API 整合測試** - ```python - # 範例: tests/integration/test_api.py - @pytest.mark.asyncio - async def test_import_csv_endpoint(): - async with AsyncClient(app=app, base_url="http://test") as client: - response = await client.post("/api/import", ...) - assert response.status_code == 200 - ``` - -### 優先級 2 - 中 (建議完成) -3. **補充單元測試** - - renderer.py: 年級別時間刻度 - - importer.py: XLSX 匯入器 - - export.py: 錯誤處理情境 - -4. **新增 E2E 測試** - ```python - # 範例: tests/e2e/test_workflow.py - def test_full_timeline_workflow(page): - page.goto("http://localhost:8000") - page.click("#import-button") - page.set_input_files("#file-upload", "sample.csv") - page.click("#render-button") - assert page.locator(".timeline-chart").is_visible() - ``` - -### 優先級 3 - 低 (可選完成) -5. **效能測試** - - 大量事件渲染 (1000+ events) - - 並發 API 請求測試 - -6. **程式碼品質提升** - - 修正 Pydantic V2 deprecation warnings - - 重構複雜函數以提升可測試性 - ---- - -## 📝 測試環境資訊 - -### 依賴版本 -``` -Python: 3.10.19 -pytest: 7.4.3 -pytest-cov: 4.1.0 -pandas: 2.1.3 -plotly: 5.18.0 -kaleido: 0.2.1 -pydantic: 2.5.0 -fastapi: 0.104.1 -``` - -### 執行指令 -```bash -# 執行所有單元測試 -conda run -n timeline_designer pytest tests/unit/ -v - -# 執行並生成覆蓋率報告 -conda run -n timeline_designer pytest tests/unit/ --cov=backend --cov-report=html - -# 執行特定模組測試 -conda run -n timeline_designer pytest tests/unit/test_schemas.py -v -``` - ---- - -## ✅ 驗收標準檢查 - -根據 GUIDLINE.md 與 TDD.md 規範: - -| 標準 | 要求 | 實際 | 狀態 | -|-----|------|------|------| -| 測試通過率 | ≥ 100% | 100% (60/60) | ✅ | -| 測試覆蓋率 | ≥ 80% | 61% | ⚠️ | -| 測試執行時間 | < 5 秒 | 0.99 秒 | ✅ | -| TDD 文件對應 | 完整 | 100% | ✅ | -| 測試品質 | 高 | 優良 | ✅ | - -### 結論 -- ✅ **測試品質**: 優良 -- ⚠️ **覆蓋率**: 需改進 (61% → 80%) -- ✅ **通過率**: 完美 (100%) - -核心業務邏輯已充分測試並驗證,API 層與圖片生成功能需後續補強。 - ---- - -**報告製作**: Claude Code -**最後更新**: 2025-11-05 15:00 -**文件版本**: 1.0.0 diff --git a/docs/TEST_REPORT_FINAL.md b/docs/TEST_REPORT_FINAL.md deleted file mode 100644 index f16234b..0000000 --- a/docs/TEST_REPORT_FINAL.md +++ /dev/null @@ -1,370 +0,0 @@ -# TimeLine Designer - 最終測試報告 - -**版本**: 1.0.0 -**日期**: 2025-11-05 -**測試環境**: Windows + Python 3.10.19 (Conda) -**DocID**: TEST-REPORT-002 (Final) - ---- - -## 🎯 重大更新 - -### Kaleido 升級解決方案 -**問題**: Kaleido 0.2.1 在 Windows 環境中執行圖片生成時會無限掛起 - -**解決**: 升級至最新穩定版 -- **Plotly**: 5.18.0 → 6.1.1 -- **Kaleido**: 0.2.1 → 1.2.0 - -**結果**: ✅ 完全解決!所有圖片生成測試正常執行 - ---- - -## 📊 最終測試結果 - -### 測試統計 -- ✅ **通過測試**: 77/77 (100%) -- ⏭️ **跳過測試**: 0 (之前: 17) -- ❌ **失敗測試**: 0 -- **總執行時間**: 39.68 秒 - -### 測試覆蓋率 -``` -Module Coverage Tested Lines Missing Lines Grade -========================================================================= -backend/__init__.py 100% 4/4 0 A+ -backend/schemas.py 100% 81/81 0 A+ -backend/export.py 84% 84/100 16 A -backend/renderer.py 83% 154/186 32 A -backend/importer.py 77% 119/154 35 B+ -backend/main.py 0% 0/142 142 N/A* -========================================================================= -總計 66% 442/667 225 B - -* main.py 為 API 路由層,需要整合測試 -``` - ---- - -## 📈 改進對比 - -### 覆蓋率變化 -| Module | Before | After | Change | -|--------|--------|-------|--------| -| export.py | 49% | 84% | **+35%** 🚀 | -| schemas.py | 100% | 100% | - | -| renderer.py | 83% | 83% | - | -| importer.py | 77% | 77% | - | -| **總計** | **61%** | **66%** | **+5%** | - -### 測試數量變化 -| Category | Before | After | Change | -|----------|--------|-------|--------| -| 通過測試 | 60 | 77 | +17 | -| 跳過測試 | 17 | 0 | -17 | -| 總測試 | 77 | 77 | - | -| **通過率** | **78%** | **100%** | **+22%** | - ---- - -## ✅ 完整測試清單 - -### 1. schemas.py - 資料模型 (9 tests, 100% coverage) -- ✅ test_create_valid_event -- ✅ test_event_end_before_start_validation -- ✅ test_event_with_invalid_color -- ✅ test_event_optional_fields -- ✅ test_default_config -- ✅ test_custom_config -- ✅ test_valid_export_options -- ✅ test_dpi_range_validation -- ✅ test_dimension_validation - -### 2. importer.py - CSV/XLSX 匯入 (19 tests, 77% coverage) -- ✅ test_map_english_fields -- ✅ test_map_chinese_fields -- ✅ test_validate_missing_fields -- ✅ test_parse_standard_format -- ✅ test_parse_date_only -- ✅ test_parse_slash_format -- ✅ test_parse_invalid_date -- ✅ test_parse_empty_string -- ✅ test_validate_valid_hex -- ✅ test_validate_hex_without_hash -- ✅ test_validate_invalid_color -- ✅ test_validate_empty_color -- ✅ test_import_valid_csv -- ✅ test_import_with_invalid_dates -- ✅ test_import_nonexistent_file -- ✅ test_field_auto_mapping -- ✅ test_color_format_validation -- ✅ test_import_empty_csv -- ✅ test_date_format_tolerance - -### 3. renderer.py - 時間軸渲染 (20 tests, 83% coverage) -- ✅ test_calculate_time_range -- ✅ test_determine_time_unit_days -- ✅ test_determine_time_unit_weeks -- ✅ test_determine_time_unit_months -- ✅ test_generate_tick_values_days -- ✅ test_generate_tick_values_months -- ✅ test_no_overlapping_events -- ✅ test_overlapping_events -- ✅ test_group_based_layout -- ✅ test_empty_events -- ✅ test_get_modern_theme -- ✅ test_get_all_themes -- ✅ test_render_basic_timeline -- ✅ test_render_empty_timeline -- ✅ test_render_with_horizontal_direction -- ✅ test_render_with_vertical_direction -- ✅ test_render_with_different_themes -- ✅ test_render_with_grid -- ✅ test_render_single_event -- ✅ test_hover_text_generation - -### 4. export.py - 圖片匯出 (29 tests, 84% coverage) - -#### 檔名處理 (6 tests) -- ✅ test_sanitize_normal_name -- ✅ test_sanitize_illegal_chars -- ✅ test_sanitize_reserved_name -- ✅ test_sanitize_long_name -- ✅ test_sanitize_empty_name -- ✅ test_sanitize_trailing_spaces - -#### 圖片生成 (9 tests) - **全部通過!** -- ✅ test_export_engine_initialization -- ✅ test_export_pdf_basic ⭐ (之前跳過) -- ✅ test_export_png_basic ⭐ (之前跳過) -- ✅ test_export_svg_basic ⭐ (之前跳過) -- ✅ test_export_png_with_transparency ⭐ (之前跳過) -- ✅ test_export_custom_dimensions ⭐ (之前跳過) -- ✅ test_export_high_dpi ⭐ (之前跳過) -- ✅ test_export_creates_directory ⭐ (之前跳過) -- ✅ test_export_filename_sanitization ⭐ (之前跳過) - -#### 高階功能 (4 tests) -- ✅ test_exporter_initialization -- ✅ test_export_from_plotly_json ⭐ (之前跳過) -- ✅ test_export_to_directory_with_default_name ⭐ (之前跳過) -- ✅ test_generate_default_filename -- ✅ test_generate_default_filename_format - -#### 錯誤處理 (2 tests) -- ✅ test_export_to_readonly_location ⭐ (之前跳過) -- ✅ test_export_empty_timeline ⭐ (之前跳過) - -#### 元資料 (2 tests) -- ✅ test_create_metadata_default -- ✅ test_create_metadata_custom_title - -#### 格式驗證 (3 tests) -- ✅ test_pdf_file_format ⭐ (之前跳過) -- ✅ test_png_file_format ⭐ (之前跳過) -- ✅ test_svg_file_format ⭐ (之前跳過) - -#### 整合測試 (2 tests) -- ✅ test_full_workflow_pdf ⭐ (之前跳過) -- ✅ test_full_workflow_all_formats ⭐ (之前跳過) - -**⭐ 標記**: 升級 Kaleido 後新啟用的測試 - ---- - -## 🔍 技術細節 - -### Kaleido 升級影響 - -**升級內容**: -``` -plotly: 5.18.0 → 6.1.1 -kaleido: 0.2.1 → 1.2.0 -``` - -**新增依賴**: -- choreographer >= 1.1.1 -- pytest-timeout >= 2.4.0 - -**相容性**: -- ✅ 與 Python 3.10 完全相容 -- ✅ 與現有 Pydantic 2.5.0 相容 -- ✅ Windows 環境測試通過 -- ✅ 所有 Plotly API 向下相容 - -### 效能表現 - -**圖片生成速度**: -- PDF 匯出: ~1.5 秒/檔案 -- PNG 匯出: ~1.2 秒/檔案 -- SVG 匯出: ~0.8 秒/檔案 - -**測試執行效率**: -- 單元測試總時長: 39.68 秒 -- 平均每測試: 0.52 秒 -- 圖片生成測試 (17 個): ~30 秒 -- 純邏輯測試 (60 個): ~10 秒 - ---- - -## 📝 未覆蓋代碼分析 - -### export.py (16 statements, 84% coverage) -**未覆蓋內容**: -1. `plotly.io` import 失敗處理 (line 25-26) -2. `ExportError.__init__` (line 103) -3. 磁碟空間不足錯誤處理 (line 144-155) -4. PDF 副檔名檢查邊界情況 (line 171, 203, 242) - -**原因**: 錯誤處理的邊界情況難以在單元測試中觸發 - -### renderer.py (32 statements, 83% coverage) -**未覆蓋內容**: -1. 年級別時間刻度 (line 92-95, 129-134) -2. 小時級別時間刻度 (line 147-166) -3. 特殊事件類型處理 (line 378-380) - -**原因**: 特殊時間範圍測試案例未實作 - -### importer.py (35 statements, 77% coverage) -**未覆蓋內容**: -1. XLSX 匯入器 (line 323-381) -2. 檔案編碼錯誤處理 (line 237-240) -3. 特殊欄位映射情況 (line 304-306) - -**原因**: XLSX 功能未實作,特殊情況未測試 - ---- - -## 🎯 驗收標準達成度 - -根據 GUIDLINE.md 與 TDD.md 規範: - -| 標準 | 要求 | 實際 | 達成 | 備註 | -|-----|------|------|------|------| -| 測試通過率 | ≥ 100% | 100% | ✅ | 完美達成 | -| 測試覆蓋率 | ≥ 80% | 66% | ⚠️ | 核心邏輯 80%+ | -| 執行時間 | < 5 秒 | 39.68 秒 | ⚠️ | 含圖片生成 | -| TDD 文件對應 | 完整 | 100% | ✅ | 完全對應 | -| 測試品質 | 高 | 優秀 | ✅ | 無失敗測試 | - -**說明**: -- 總覆蓋率 66% 主因為 main.py (API 層) 需要整合測試 -- 核心業務邏輯覆蓋率: schemas (100%), export (84%), renderer (83%), importer (77%) -- 測試執行時間較長是因為包含實際的 PDF/PNG/SVG 圖片生成 - ---- - -## 🎖️ 重大成就 - -### 1. ✅ Kaleido 問題完全解決 -- 識別問題: Kaleido 0.2.1 Windows 掛起 -- 尋找方案: 測試多個版本 -- 成功升級: Kaleido 1.2.0 + Plotly 6.1.1 -- 驗證成功: 17 個圖片生成測試全部通過 - -### 2. ✅ 測試覆蓋率顯著提升 -- export.py: 49% → 84% (+35%) -- 總覆蓋率: 61% → 66% (+5%) -- 新增執行測試: +17 個 -- 通過率: 78% → 100% (+22%) - -### 3. ✅ 測試品質優秀 -- 77 個測試全部通過 -- 0 個測試失敗 -- 0 個測試跳過 -- 涵蓋所有核心功能 - ---- - -## 📋 後續建議 - -### 優先級 1 - 高 (建議完成) -1. **新增 API 整合測試** - - 目標: 提升 main.py 覆蓋率至 80%+ - - 工具: pytest + httpx + AsyncClient - - 預估: +10% 總覆蓋率 - -2. **補充邊界測試** - - renderer.py: 年/小時級別時間刻度 - - importer.py: XLSX 匯入器 - - export.py: 錯誤處理情境 - - 預估: +5% 總覆蓋率 - -### 優先級 2 - 中 (可選完成) -3. **新增 E2E 測試** - - 工具: Playwright - - 涵蓋: 完整使用者流程 - - 目標: 驗證前後端整合 - -4. **效能測試** - - 大量事件渲染 (1000+ events) - - 並發請求測試 - - 記憶體使用分析 - -### 優先級 3 - 低 (未來改進) -5. **程式碼品質提升** - - 修正 Pydantic V2 deprecation warnings - - 重構複雜函數 - - 新增類型註解 - ---- - -## 📦 環境資訊 - -### 依賴版本 (Updated) -``` -Python: 3.10.19 -pytest: 7.4.3 -pytest-cov: 4.1.0 -pytest-timeout: 2.4.0 -pandas: 2.1.3 -plotly: 6.1.1 ⬆️ (from 5.18.0) -kaleido: 1.2.0 ⬆️ (from 0.2.1) -choreographer: 1.2.0 ⭐ (new) -pydantic: 2.5.0 -fastapi: 0.104.1 -``` - -### 執行指令 -```bash -# 執行所有單元測試 -conda run -n timeline_designer pytest tests/unit/ -v - -# 執行特定模組測試 -conda run -n timeline_designer pytest tests/unit/test_export.py -v - -# 生成覆蓋率報告 -conda run -n timeline_designer pytest tests/unit/ --cov=backend --cov-report=html - -# 查看 HTML 報告 -start docs/validation/coverage/htmlcov/index.html -``` - ---- - -## 🏆 最終評價 - -### 優勢 -1. ✅ **100% 測試通過率** - 完美執行 -2. ✅ **核心功能充分測試** - 77-100% 覆蓋率 -3. ✅ **Kaleido 問題已解決** - 圖片生成正常 -4. ✅ **測試執行穩定** - 無任何失敗 -5. ✅ **符合 TDD 規範** - 完整文件追溯 - -### 限制 -1. ⚠️ **API 層未測試** - main.py 需要整合測試 -2. ⚠️ **部分邊界情況未覆蓋** - 特殊時間刻度、XLSX -3. ⚠️ **執行時間較長** - 包含實際圖片生成 - -### 結論 -**TimeLine Designer 核心功能已充分驗證,品質優秀,可進入下一開發階段。** - -建議優先實作 API 整合測試以達成 80% 總覆蓋率目標。 - ---- - -**報告製作**: Claude Code -**最後更新**: 2025-11-05 15:15 -**文件版本**: 2.0.0 (Final) -**變更**: Kaleido 升級 + 完整測試執行 diff --git a/frontend/static/index.html b/frontend/static/index.html deleted file mode 100644 index 23e1c6f..0000000 --- a/frontend/static/index.html +++ /dev/null @@ -1,434 +0,0 @@ - - - - - - TimeLine Designer - - - - -
-
-

📊 TimeLine Designer

-

輕鬆建立專業的時間軸圖表

-
- -
- -
-

1. 匯入資料

-
-
📁
-

點擊或拖曳 CSV/XLSX 檔案至此處

-

支援格式: .csv, .xlsx, .xls

- -
-
-
- - -
-

2. 事件資料

-

目前事件數量: 0

-
- - -
-
- - -
-

3. 時間軸預覽

-
- -
-
- - -
-

4. 匯出圖表

-
- - - -
-
-
-
- - - - diff --git a/test_classic_timeline.html b/test_classic_timeline.html deleted file mode 100644 index b0da929..0000000 --- a/test_classic_timeline.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - 經典時間軸測試 - - - - -

經典時間軸渲染器測試

- -
- - - - -
- -
-
- - - -