diff --git a/src/public/index.html b/src/public/index.html index 72836d1..e67c189 100644 --- a/src/public/index.html +++ b/src/public/index.html @@ -46,6 +46,11 @@
${translate( - "error_loading_graph_d3" // Use a specific key - )}
`; + dependencyGraphElement.innerHTML = `${translate("error_loading_graph_d3")}
`; } } return; } + updateDimensions(); + // 如果沒有任務,清空圖表並顯示提示 if (tasks.length === 0) { - dependencyGraphElement.innerHTML = `${translate( - "dependency_graph_placeholder_empty" - )}
`; - // 重置 SVG 和 simulation 變數,以便下次正確初始化 + dependencyGraphElement.innerHTML = `${translate("dependency_graph_placeholder_empty")}
`; svg = null; g = null; simulation = null; - isGraphInitialized = false; // << 新增:重置初始化標誌 return; } @@ -775,10 +838,9 @@ function renderDependencyGraph() { id: task.id, name: task.name, status: task.status, - // 保留現有位置以便平滑過渡 x: simulation?.nodes().find((n) => n.id === task.id)?.x, y: simulation?.nodes().find((n) => n.id === task.id)?.y, - fx: simulation?.nodes().find((n) => n.id === task.id)?.fx, // 保留固定位置 + fx: simulation?.nodes().find((n) => n.id === task.id)?.fx, fy: simulation?.nodes().find((n) => n.id === task.id)?.fy, })); @@ -788,44 +850,29 @@ function renderDependencyGraph() { task.dependencies.forEach((dep) => { const sourceId = typeof dep === "object" ? dep.taskId : dep; const targetId = task.id; - if ( - nodes.some((n) => n.id === sourceId) && - nodes.some((n) => n.id === targetId) - ) { - // 確保 link 的 source/target 是 ID,以便力導向識別 + if (nodes.some((n) => n.id === sourceId) && nodes.some((n) => n.id === targetId)) { links.push({ source: sourceId, target: targetId }); } else { - console.warn( - `Dependency link ignored: Task ${sourceId} or ${targetId} not found in task list.` - ); + console.warn(`Dependency link ignored: Task ${sourceId} or ${targetId} not found in task list.`); } }); } }); - // 2. D3 繪圖設置與更新 - const width = dependencyGraphElement.clientWidth; - const height = dependencyGraphElement.clientHeight || 400; - if (!svg) { // --- 首次渲染 --- console.log("First render of dependency graph"); - dependencyGraphElement.innerHTML = ""; // 清空 placeholder + dependencyGraphElement.innerHTML = ""; - svg = d3 - .select(dependencyGraphElement) + svg = d3.select(dependencyGraphElement) .append("svg") .attr("viewBox", [0, 0, width, height]) .attr("preserveAspectRatio", "xMidYMid meet"); - g = svg.append("g"); // 主要組,用於縮放和平移 + g = svg.append("g"); - // 添加縮放和平移 - svg.call( - d3.zoom().on("zoom", (event) => { - g.attr("transform", event.transform); - }) - ); + // 初始化並添加縮放行為 + initZoom(); // 添加箭頭定義 g.append("defs") @@ -842,19 +889,12 @@ function renderDependencyGraph() { .attr("fill", "#999"); // 初始化力導向模擬 - simulation = d3 - .forceSimulation() // 初始化時不傳入 nodes - .force( - "link", - d3 - .forceLink() - .id((d) => d.id) - .distance(100) // 指定 id 訪問器 - ) + simulation = d3.forceSimulation() + .force("link", d3.forceLink().id((d) => d.id).distance(100)) .force("charge", d3.forceManyBody().strength(-300)) .force("center", d3.forceCenter(width / 2, height / 2)) .force("collide", d3.forceCollide().radius(30)) - .on("tick", ticked); // 綁定 tick 事件處理函數 + .on("tick", ticked); // 添加用於存放連結和節點的組 g.append("g").attr("class", "links"); @@ -862,7 +902,6 @@ function renderDependencyGraph() { } else { // --- 更新渲染 --- console.log("Updating dependency graph"); - // 更新 SVG 尺寸和中心力 (如果窗口大小改變) svg.attr("viewBox", [0, 0, width, height]); simulation.force("center", d3.forceCenter(width / 2, height / 2)); } @@ -1191,5 +1230,13 @@ function getStatusClass(status) { return status ? status.replace(/_/g, "-") : "unknown"; // 替換所有下劃線 } +// 新增:更新寬高的函數 +function updateDimensions() { + if (dependencyGraphElement) { + width = dependencyGraphElement.clientWidth; + height = dependencyGraphElement.clientHeight || 400; + } +} + // 函數:啟用節點拖拽 (保持不變) // ... drag ... diff --git a/src/public/style.css b/src/public/style.css index f019803..a7681e1 100644 --- a/src/public/style.css +++ b/src/public/style.css @@ -123,6 +123,32 @@ main { border-bottom: 1px solid rgba(255, 255, 255, 0.05); } +.reset-view-btn { + background: none; + border: none; + color: var(--text-color); + cursor: pointer; + padding: 8px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s ease; +} + +.reset-view-btn:hover { + background-color: rgba(255, 255, 255, 0.1); + transform: rotate(-30deg); +} + +.reset-view-btn svg { + transition: transform 0.3s ease; +} + +.reset-view-btn.resetting svg { + transform: rotate(-360deg); +} + h2 { font-size: 1.2rem; font-weight: 500;