Dependency Graph Visualization Tool
Your Dependency Graph
Visualize the relationships and dependencies between your components or tasks.
Configure Graph Data
Add, edit, or remove nodes and define their dependencies.
Add/Edit Node
| ID | Name | Type | Description | Actions |
|---|
Define New Dependency
| ID | Source Node | Target Node | Label | Actions |
|---|
No nodes configured. Go to "Data Configuration" to add components or tasks.
'; return; } const width = graphVisualizationContainer.clientWidth; const height = graphVisualizationContainer.clientHeight; // Create SVG container const svg = d3.select(graphVisualizationContainer) .append("svg") .attr("width", width) .attr("height", height) .attr("viewBox", `0 0 ${width} ${height}`) .attr("preserveAspectRatio", "xMidYMid meet"); // Define arrow markers for links svg.append("defs").append("marker") .attr("id", "arrowhead") .attr("viewBox", "-0 -5 10 10") .attr("refX", 18) // Position arrow slightly away from node .attr("refY", 0) .attr("orient", "auto") .attr("markerWidth", 8) .attr("markerHeight", 8) .attr("xoverflow", "visible") .append("svg:path") .attr("d", "M 0,-5 L 10,0 L 0,5") .attr("fill", "#999") .attr("class", "link-arrow"); // Initialize D3 force simulation // Use a copy of nodes and dependencies for the simulation to avoid modifying original data const simulationNodes = nodes.map(d => ({ ...d })); const simulationLinks = dependencies.map(d => ({ ...d })); const simulation = d3.forceSimulation(simulationNodes) .force("link", d3.forceLink(simulationLinks).id(d => d.id).distance(100)) .force("charge", d3.forceManyBody().strength(-300)) // Node repulsion .force("center", d3.forceCenter(width / 2, height / 2)); // Center the graph // Create links const link = svg.append("g") .attr("class", "links") .selectAll("line") .data(simulationLinks) .enter().append("line") .attr("class", "link") .attr("marker-end", "url(#arrowhead)"); // Add arrowhead // Create link labels (text paths) const linkLabel = svg.append("g") .attr("class", "link-labels") .selectAll("text") .data(simulationLinks) .enter().append("text") .attr("class", "link-label") .attr("dy", -3) // Adjust position above the line .text(d => d.label); // Create nodes const node = svg.append("g") .attr("class", "nodes") .selectAll("g") .data(simulationNodes) .enter().append("g") .attr("class", "node") .call(d3.drag() // Enable dragging for nodes .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)); node.append("circle") .attr("r", 10) // Node radius .attr("fill", d => { // Color nodes based on type for better visual distinction if (d.type === 'Component') return '#4CAF50'; // Green if (d.type === 'Service') return '#FFC107'; // Amber if (d.type === 'Data Source') return '#2196F3'; // Blue if (d.type === 'Task') return '#9C27B0'; // Purple return '#69b3a2'; // Default }); node.append("text") .attr("dy", -15) // Position text above circle .text(d => d.name); // Update positions on each tick of the simulation simulation.on("tick", () => { link .attr("x1", d => d.source.x) .attr("y1", d => d.source.y) .attr("x2", d => d.target.x) .attr("y2", d => d.target.y); // Calculate midpoint for link labels linkLabel .attr("x", d => (d.source.x + d.target.x) / 2) .attr("y", d => (d.source.y + d.target.y) / 2); node .attr("transform", d => `translate(${d.x},${d.y})`); }); // --- Drag functions for D3 nodes --- function dragstarted(event, d) { if (!event.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; } function dragged(event, d) { d.fx = event.x; d.fy = event.y; } function dragended(event, d) { if (!event.active) simulation.alphaTarget(0); d.fx = null; // Release fixed position d.fy = null; } } // --- PDF Download Function --- /** * Generates and downloads a PDF of the current dashboard content. */ window.downloadPdf = async function() { if (!dashboardTab) { console.error('Error: Dashboard tab content not found for PDF generation.'); return; } // Temporarily hide elements not needed in PDF const elementsToHide = document.querySelectorAll('.tab-nav, .nav-buttons, #downloadPdfButton'); elementsToHide.forEach(el => el.style.display = 'none'); // Ensure the dashboard tab is active for capture dashboardTab.classList.add('active'); // Use html2canvas to capture the graph visualization container const graphCanvas = await html2canvas(graphVisualizationContainer, { scale: 2, // Increase scale for better resolution in PDF useCORS: true, // Important for images if any, though not used here logging: false // Disable logging for cleaner console }); const graphImgData = graphCanvas.toDataURL('image/png'); // Re-show hidden elements immediately after capture elementsToHide.forEach(el => el.style.display = ''); const { jsPDF } = window.jspdf; const pdf = new jsPDF({ orientation: 'landscape', // Landscape for wider graphs unit: 'px', format: 'a4' }); const pdfWidth = pdf.internal.pageSize.getWidth(); let yOffset = 40; // Add title to PDF pdf.setFontSize(22); pdf.text("Dependency Graph Report", pdfWidth / 2, yOffset, { align: 'center' }); yOffset += 20; // Add current date/time to PDF pdf.setFontSize(10); pdf.text(`Generated on: ${new Date().toLocaleString()}`, pdfWidth / 2, yOffset, { align: 'center' }); yOffset += 40; // Add Graph Visualization Image pdf.setFontSize(18); pdf.text("Dependency Graph Diagram", pdfWidth / 2, yOffset, { align: 'center' }); yOffset += 10; const graphImgHeight = (graphCanvas.height * (pdfWidth - 40)) / graphCanvas.width; // Check if image fits on current page, add new page if not if (yOffset + graphImgHeight > pdf.internal.pageSize.getHeight() - 20) { pdf.addPage(); yOffset = 40; // Reset yOffset for new page } pdf.addImage(graphImgData, 'PNG', 20, yOffset, pdfWidth - 40, graphImgHeight); yOffset += graphImgHeight + 30; // Add a section for detailed Node data (tabular format) pdf.addPage(); pdf.setFontSize(18); pdf.text("Detailed Node Information", pdf.internal.pageSize.getWidth() / 2, 40, { align: 'center' }); const nodeTableData = nodes.map(n => [ n.id, n.name, n.type, n.description || 'N/A' ]); pdf.autoTable({ head: [['ID', 'Name', 'Type', 'Description']], body: nodeTableData, startY: 60, theme: 'grid', styles: { fontSize: 9, cellPadding: 4 }, headStyles: { fillColor: [242, 242, 242], textColor: [51, 51, 51], fontStyle: 'bold' }, alternateRowStyles: { fillColor: [251, 251, 251] }, margin: { top: 70, left: 20, right: 20 } }); // Add a section for detailed Dependency data (tabular format) pdf.addPage(); pdf.setFontSize(18); pdf.text("Detailed Dependency Information", pdf.internal.pageSize.getWidth() / 2, 40, { align: 'center' }); const dependencyTableData = dependencies.map(dep => [ dep.id, nodes.find(n => n.id === dep.source)?.name || dep.source, nodes.find(n => n.id === dep.target)?.name || dep.target, dep.label ]); pdf.autoTable({ head: [['ID', 'Source Node', 'Target Node', 'Label']], body: dependencyTableData, startY: 60, theme: 'grid', styles: { fontSize: 9, cellPadding: 4 }, headStyles: { fillColor: [242, 242, 242], textColor: [51, 51, 51], fontStyle: 'bold' }, alternateRowStyles: { fillColor: [251, 251, 251] }, margin: { top: 70, left: 20, right: 20 } }); pdf.save('dependency_graph_report.pdf'); }; // --- Event Listeners and Initial Render --- downloadPdfButton.addEventListener('click', downloadPdf); // Initial setup updateNodesTable(); updateDependenciesTable(); populateNodeSelects(); renderGraphVisualization(); updateNavigationButtons(); // Set initial button states });