Decision Tree Visualization Tool
Your Decision Tree
Visualize the flow of decisions and outcomes.
Configure Decision Tree Nodes
Add, edit, or remove decision and outcome nodes. Define their relationships.
Add/Edit Node
(Optional: Describes the path from the parent to this node)
| ID | Text | Type | Parent ID | Branch Condition | Actions |
|---|
No nodes configured. Go to "Data Configuration" to add your decision tree.
'; return; } // Convert flat data to hierarchical data for D3 tree layout const dataMap = new Map(nodes.map(node => [node.id, { ...node }])); let treeData = null; // Find the root node (node with no parent) const rootNode = nodes.find(node => node.parentNodeId === null); if (!rootNode) { treeVisualizationContainer.innerHTML = 'Error: No root node found. Please ensure one node has "No Parent" selected in Data Configuration.
'; return; } // Build the hierarchical structure dataMap.forEach(node => { if (node.parentNodeId) { const parent = dataMap.get(node.parentNodeId); if (parent) { if (!parent.children) { parent.children = []; } parent.children.push(node); } } }); treeData = dataMap.get(rootNode.id); const margin = { top: 50, right: 90, bottom: 50, left: 90 }; const width = treeVisualizationContainer.clientWidth - margin.left - margin.right; const height = treeVisualizationContainer.clientHeight - margin.top - margin.bottom; const svg = d3.select(treeVisualizationContainer) .append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", `translate(${margin.left},${margin.top})`); const treemap = d3.tree().size([width, height]); let root = d3.hierarchy(treeData, d => d.children); root = treemap(root); // Add links (paths) const link = svg.selectAll(".link") .data(root.links()) .enter().append("path") .attr("class", "link") .attr("d", d3.linkVertical() .x(d => d.x) .y(d => d.y)); // Add link labels (branch conditions) svg.selectAll(".link-text") .data(root.links()) .enter().append("text") .attr("class", "link-text") .attr("x", d => (d.source.x + d.target.x) / 2) .attr("y", d => (d.source.y + d.target.y) / 2) .attr("dy", -5) // Adjust position slightly above the line .text(d => d.target.data.branchCondition || ''); // Add nodes const node = svg.selectAll(".node") .data(root.descendants()) .enter().append("g") .attr("class", d => "node" + (d.children ? " node--internal" : " node--leaf")) .attr("transform", d => `translate(${d.x},${d.y})`); node.append("circle") .attr("r", 10) .style("fill", d => d.data.type === 'Decision' ? '#007bff' : '#28a745'); // Blue for Decision, Green for Outcome node.append("text") .attr("dy", ".35em") .attr("y", d => d.children ? -20 : 20) // Position text above for internal, below for leaf .attr("text-anchor", "middle") .text(d => d.data.text); } // --- 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'); // Capture the visualization container const vizCanvas = await html2canvas(treeVisualizationContainer, { scale: 2, // Increase scale for better resolution in PDF useCORS: true, logging: false, scrollX: -window.scrollX, // Capture current scroll position scrollY: -window.scrollY, windowWidth: document.documentElement.offsetWidth, windowHeight: document.documentElement.offsetHeight }); const vizImgData = vizCanvas.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 trees unit: 'px', format: 'a4' }); const pdfWidth = pdf.internal.pageSize.getWidth(); let yOffset = 40; // Add title to PDF pdf.setFontSize(22); pdf.text("Decision Tree Visualization 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 Visualization Image pdf.setFontSize(18); pdf.text("Decision Tree Diagram", pdfWidth / 2, yOffset, { align: 'center' }); yOffset += 10; const vizImgHeight = (vizCanvas.height * (pdfWidth - 40)) / vizCanvas.width; // Ensure image fits on page, add new page if needed if (yOffset + vizImgHeight > pdf.internal.pageSize.getHeight() - 20) { pdf.addPage(); yOffset = 40; // Reset yOffset for new page } pdf.addImage(vizImgData, 'PNG', 20, yOffset, pdfWidth - 40, vizImgHeight); yOffset += vizImgHeight + 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 tableData = nodes.map(n => [ n.id, n.text, n.type, n.parentNodeId || 'N/A (Root)', n.branchCondition || 'N/A' ]); pdf.autoTable({ head: [['ID', 'Text', 'Type', 'Parent ID', 'Branch Condition']], body: tableData, 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('decision_tree_report.pdf'); }; // --- Event Listeners and Initial Render --- downloadPdfButton.addEventListener('click', downloadPdf); parentNodeIdSelect.addEventListener('change', toggleParentSelect); // Listen for changes on parent select // Initial setup updateConfigTable(); populateParentNodeSelect(); renderDecisionTreeVisualization(); updateNavigationButtons(); // Set initial button states toggleParentSelect(); // Initial call to set form field visibility });