diff --git "a/ml_complete-all-topics/app.js" "b/ml_complete-all-topics/app.js" --- "a/ml_complete-all-topics/app.js" +++ "b/ml_complete-all-topics/app.js" @@ -13,7 +13,7 @@ function logViz(module, name, status, error = null) { timestamp: new Date().toLocaleTimeString(), error: error }; - + if (status === 'success') { vizLog.success.push(log); console.log(`✓ ${module} - ${name}`); @@ -34,24 +34,24 @@ function createVerifiedVisualization(canvasId, chartConfig, moduleName, vizName) showFallback(canvasId, 'error'); return null; } - + const ctx = canvas.getContext('2d'); if (!ctx) { logViz(moduleName, vizName, 'failed', 'Cannot get context'); showFallback(canvasId, 'error'); return null; } - + if (typeof Chart === 'undefined') { logViz(moduleName, vizName, 'failed', 'Chart.js not loaded'); showFallback(canvasId, 'error'); return null; } - + const chart = new Chart(ctx, chartConfig); logViz(moduleName, vizName, 'success'); return chart; - + } catch (error) { logViz(moduleName, vizName, 'failed', error.message); showFallback(canvasId, 'error'); @@ -62,10 +62,10 @@ function createVerifiedVisualization(canvasId, chartConfig, moduleName, vizName) function showFallback(elementId, type) { const element = document.getElementById(elementId); if (!element) return; - + const container = element.parentElement; if (!container) return; - + if (type === 'error') { container.innerHTML = '
⚠️ Visualization temporarily unavailable
Data is still accessible via diagnostic tools
'; } @@ -78,20 +78,40 @@ window.addEventListener('load', () => { console.log(`✓ Success: ${vizLog.success.length}`); console.log(`✗ Failed: ${vizLog.failed.length}`); console.log(`⚠ Warnings: ${vizLog.warnings.length}`); - + if (vizLog.failed.length > 0) { console.error('Failed visualizations:', vizLog.failed); } - + if (vizLog.success.length > 0) { console.log('\nSuccessful visualizations:'); vizLog.success.forEach(v => console.log(` ✓ ${v.module} - ${v.name}`)); } - + console.log('\n========================================='); }, 2000); }); + +// Helper for robust initialization +function ensureCanvasVisible(canvasId, callback) { + const canvas = document.getElementById(canvasId); + if (!canvas) return; + + // If already initialized AND has valid width, stop + if (canvas.dataset.initialized === 'true' && canvas.offsetWidth > 100) return; + + // If width is too small, retry + if (canvas.offsetWidth < 100) { + setTimeout(() => ensureCanvasVisible(canvasId, callback), 200); + return; + } + + // Mark and execute + canvas.dataset.initialized = 'true'; + callback(); +} + // Data const data = { linearRegression: [ @@ -162,13 +182,13 @@ let state = { // Initialize category navigation function initCategories() { const categoryHeaders = document.querySelectorAll('.toc-category-header'); - + categoryHeaders.forEach(header => { header.addEventListener('click', () => { const category = header.getAttribute('data-category'); const content = document.getElementById(`${category}-content`); const toggle = header.querySelector('.category-toggle'); - + if (content.classList.contains('collapsed')) { content.classList.remove('collapsed'); toggle.classList.remove('collapsed'); @@ -178,7 +198,7 @@ function initCategories() { } }); }); - + // Start with all categories expanded document.querySelectorAll('.toc-category-content').forEach(content => { content.classList.remove('collapsed'); @@ -188,12 +208,12 @@ function initCategories() { // Initialize collapsible sections function initSections() { const sections = document.querySelectorAll('.section'); - + sections.forEach(section => { const header = section.querySelector('.section-header'); const toggle = section.querySelector('.section-toggle'); const body = section.querySelector('.section-body'); - + // Start with first section expanded if (section.id === 'intro') { body.classList.add('expanded'); @@ -201,17 +221,17 @@ function initSections() { } else { toggle.classList.add('collapsed'); } - + header.addEventListener('click', () => { const isExpanded = body.classList.contains('expanded'); - + if (isExpanded) { body.classList.remove('expanded'); toggle.classList.add('collapsed'); } else { body.classList.add('expanded'); toggle.classList.remove('collapsed'); - + // Initialize visualizations when section opens if (section.id === 'linear-regression') initLinearRegression(); if (section.id === 'gradient-descent') initGradientDescent(); @@ -254,33 +274,81 @@ function initSections() { }); } +// Helper function to initialize visualizations for a section by ID +function initSectionVisualizations(sectionId) { + // Delay to allow section to expand first + setTimeout(() => { + if (sectionId === 'linear-regression') initLinearRegression(); + if (sectionId === 'gradient-descent') initGradientDescent(); + if (sectionId === 'logistic-regression') initLogistic(); + if (sectionId === 'svm') initSVM(); + if (sectionId === 'knn') initKNN(); + if (sectionId === 'model-evaluation') initModelEvaluation(); + if (sectionId === 'regularization') initRegularization(); + if (sectionId === 'bias-variance') initBiasVariance(); + if (sectionId === 'cross-validation') initCrossValidation(); + if (sectionId === 'preprocessing') initPreprocessing(); + if (sectionId === 'loss-functions') initLossFunctions(); + if (sectionId === 'optimal-k') initOptimalK(); + if (sectionId === 'hyperparameter-tuning') initHyperparameterTuning(); + if (sectionId === 'naive-bayes') initNaiveBayes(); + if (sectionId === 'kmeans') initKMeans(); + if (sectionId === 'decision-tree-regression') initDecisionTreeRegression(); + if (sectionId === 'decision-trees') initDecisionTrees(); + if (sectionId === 'gradient-boosting') initGradientBoosting(); + if (sectionId === 'xgboost') initXGBoost(); + if (sectionId === 'bagging') initBagging(); + if (sectionId === 'boosting-adaboost') initBoostingAdaBoost(); + if (sectionId === 'random-forest') initRandomForest(); + if (sectionId === 'ensemble-methods') initEnsembleMethods(); + if (sectionId === 'gradient-boosting-classification') initGradientBoostingClassification(); + if (sectionId === 'xgboost-classification') initXGBoostClassification(); + if (sectionId === 'hierarchical-clustering') initHierarchicalClustering(); + if (sectionId === 'dbscan') initDBSCAN(); + if (sectionId === 'clustering-evaluation') initClusteringEvaluation(); + if (sectionId === 'algorithm-comparison') initAlgorithmComparison(); + }, 300); +} + // Smooth scroll for TOC links function initTOCLinks() { const links = document.querySelectorAll('.toc-link'); - + links.forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const targetId = link.getAttribute('href').substring(1); const target = document.getElementById(targetId); - + if (target) { // Remove active from all links links.forEach(l => l.classList.remove('active')); link.classList.add('active'); - - // Scroll to target - target.scrollIntoView({ behavior: 'smooth', block: 'start' }); - - // Expand the section + + // Expand the section first const toggle = target.querySelector('.section-toggle'); const body = target.querySelector('.section-body'); - body.classList.add('expanded'); - toggle.classList.remove('collapsed'); + if (body && !body.classList.contains('expanded')) { + body.classList.add('expanded'); + if (toggle) toggle.classList.remove('collapsed'); + + // Initialize visualizations for this section + initSectionVisualizations(targetId); + } + + // Scroll to target with offset for better visibility + setTimeout(() => { + const offset = 20; + const elementPosition = target.getBoundingClientRect().top + window.pageYOffset; + window.scrollTo({ + top: elementPosition - offset, + behavior: 'smooth' + }); + }, 50); } }); }); - + // Update active link on scroll let ticking = false; window.addEventListener('scroll', () => { @@ -297,12 +365,12 @@ function initTOCLinks() { function updateActiveLink() { const sections = document.querySelectorAll('.section'); const scrollPos = window.scrollY + 100; - + sections.forEach(section => { const top = section.offsetTop; const height = section.offsetHeight; const id = section.getAttribute('id'); - + if (scrollPos >= top && scrollPos < top + height) { document.querySelectorAll('.toc-link').forEach(link => { link.classList.remove('active'); @@ -318,13 +386,19 @@ function updateActiveLink() { function initLinearRegression() { const canvas = document.getElementById('lr-canvas'); if (!canvas || canvas.dataset.initialized) return; + + if (canvas.offsetWidth === 0) { + setTimeout(initLinearRegression, 100); + return; + } + canvas.dataset.initialized = 'true'; - + const slopeSlider = document.getElementById('slope-slider'); const interceptSlider = document.getElementById('intercept-slider'); const slopeVal = document.getElementById('slope-val'); const interceptVal = document.getElementById('intercept-val'); - + if (slopeSlider) { slopeSlider.addEventListener('input', (e) => { state.slope = parseFloat(e.target.value); @@ -332,7 +406,7 @@ function initLinearRegression() { drawLinearRegression(); }); } - + if (interceptSlider) { interceptSlider.addEventListener('input', (e) => { state.intercept = parseFloat(e.target.value); @@ -340,7 +414,7 @@ function initLinearRegression() { drawLinearRegression(); }); } - + drawLinearRegression(); } @@ -349,20 +423,20 @@ let lrChart = null; function drawLinearRegression() { const canvas = document.getElementById('lr-canvas'); if (!canvas) return; - + // Destroy existing chart if (lrChart) { lrChart.destroy(); } - + const ctx = canvas.getContext('2d'); - + // Calculate fitted line points const fittedLine = []; for (let x = 0; x <= 7; x += 0.1) { fittedLine.push({ x: x, y: state.slope * x + state.intercept }); } - + // Calculate MSE let mse = 0; data.linearRegression.forEach(point => { @@ -371,12 +445,12 @@ function drawLinearRegression() { mse += error * error; }); mse /= data.linearRegression.length; - + // Destroy existing chart if (lrChart) { lrChart.destroy(); } - + lrChart = createVerifiedVisualization('lr-canvas', { type: 'scatter', data: { @@ -434,31 +508,37 @@ function drawLinearRegression() { function initGradientDescent() { const canvas = document.getElementById('gd-canvas'); if (!canvas || canvas.dataset.initialized) return; + + if (canvas.offsetWidth === 0) { + setTimeout(initGradientDescent, 100); + return; + } + canvas.dataset.initialized = 'true'; - + const runBtn = document.getElementById('run-gd'); const resetBtn = document.getElementById('reset-gd'); const lrSlider = document.getElementById('lr-slider'); const lrVal = document.getElementById('lr-val'); - + if (lrSlider) { lrSlider.addEventListener('input', (e) => { state.learningRate = parseFloat(e.target.value); lrVal.textContent = state.learningRate.toFixed(2); }); } - + if (runBtn) { runBtn.addEventListener('click', runGradientDescent); } - + if (resetBtn) { resetBtn.addEventListener('click', () => { state.gdIterations = []; drawGradientDescent(); }); } - + drawGradientDescent(); } @@ -467,11 +547,11 @@ function runGradientDescent() { let m = 0, c = 20; // Start with poor values const alpha = state.learningRate; const iterations = 50; - + for (let i = 0; i < iterations; i++) { let dm = 0, dc = 0; const n = data.linearRegression.length; - + // Calculate gradients data.linearRegression.forEach(point => { const predicted = m * point.experience + c; @@ -479,11 +559,11 @@ function runGradientDescent() { dm += (2 / n) * error * point.experience; dc += (2 / n) * error; }); - + // Update parameters m -= alpha * dm; c -= alpha * dc; - + // Calculate loss let loss = 0; data.linearRegression.forEach(point => { @@ -492,10 +572,10 @@ function runGradientDescent() { loss += error * error; }); loss /= n; - + state.gdIterations.push({ m, c, loss }); } - + animateGradientDescent(); } @@ -506,15 +586,15 @@ function animateGradientDescent() { clearInterval(interval); return; } - + const iteration = state.gdIterations[step]; state.slope = iteration.m; state.intercept = iteration.c; - + // Update linear regression chart drawLinearRegression(); drawGradientDescent(step); - + step++; }, 50); } @@ -524,7 +604,7 @@ let gdChart = null; function drawGradientDescent(currentStep = -1) { const canvas = document.getElementById('gd-canvas'); if (!canvas) return; - + if (state.gdIterations.length === 0) { const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); @@ -534,20 +614,20 @@ function drawGradientDescent(currentStep = -1) { ctx.fillText('Click "Run Gradient Descent" to see the algorithm in action', canvas.width / 2, canvas.height / 2); return; } - + // Destroy existing chart if (gdChart) { gdChart.destroy(); } - + const ctx = canvas.getContext('2d'); const lossData = state.gdIterations.map((iter, i) => ({ x: i + 1, y: iter.loss })); - + // Destroy existing chart if (gdChart) { gdChart.destroy(); } - + gdChart = createVerifiedVisualization('gd-canvas', { type: 'line', data: { @@ -614,11 +694,11 @@ function verifyAllLinks() { const links = document.querySelectorAll('a[href^="#"]'); const broken = []; let working = 0; - + links.forEach(link => { const targetId = link.getAttribute('href').substring(1); const target = document.getElementById(targetId); - + if (!target) { broken.push({ text: link.textContent, @@ -631,7 +711,7 @@ function verifyAllLinks() { link.addEventListener('click', (e) => { e.preventDefault(); target.scrollIntoView({ behavior: 'smooth' }); - + // Highlight section const originalBg = target.style.backgroundColor; target.style.backgroundColor = 'rgba(106, 169, 255, 0.2)'; @@ -641,7 +721,7 @@ function verifyAllLinks() { }); } }); - + console.log(`\n=== LINK VERIFICATION ===`); console.log(`✓ Working: ${working}/${links.length}`); console.log(`✗ Broken: ${broken.length}`); @@ -656,15 +736,13 @@ function init() { initCategories(); initSections(); initTOCLinks(); - + // Initialize first section visualizations - setTimeout(() => { - initLinearRegression(); - }, 100); - + // initLinearRegression call removed as it should be handled by section expansion logic + // Verify all links on load setTimeout(verifyAllLinks, 1000); - + // Initialize diagnostics refresh setInterval(() => { const diagSection = document.getElementById('diagnostics'); @@ -680,24 +758,86 @@ if (document.readyState === 'loading') { init(); } +// SVM Visualizations // SVM Visualizations function initSVM() { - initSVMBasic(); - initSVMMargin(); - initSVMCParameter(); - initSVMTraining(); - initSVMKernel(); + ensureCanvasVisible('svm-basic-canvas', initSVMBasic); + ensureCanvasVisible('svm-margin-canvas', initSVMMargin); + ensureCanvasVisible('svm-c-canvas', initSVMCParameter); + ensureCanvasVisible('svm-training-canvas', initSVMTraining); + ensureCanvasVisible('svm-kernel-canvas', initSVMKernel); +} + +function initSVMBasicWithRetry(retries = 3) { + const canvas = document.getElementById('svm-basic-canvas'); + if (!canvas) return; + + if (canvas.offsetWidth > 100 || retries === 0) { + initSVMBasic(); + } else { + setTimeout(() => initSVMBasicWithRetry(retries - 1), 500); + } +} + +function initSVMMarginWithRetry(retries = 3) { + const canvas = document.getElementById('svm-margin-canvas'); + if (!canvas) return; + + if (canvas.offsetWidth > 100 || retries === 0) { + initSVMMargin(); + } else { + setTimeout(() => initSVMMarginWithRetry(retries - 1), 500); + } +} + +function initSVMCParameterWithRetry(retries = 3) { + const canvas = document.getElementById('svm-c-canvas'); + if (!canvas) return; + + if (canvas.offsetWidth > 100 || retries === 0) { + initSVMCParameter(); + } else { + setTimeout(() => initSVMCParameterWithRetry(retries - 1), 500); + } +} + +function initSVMTrainingWithRetry(retries = 3) { + const canvas = document.getElementById('svm-train-canvas'); + if (!canvas) return; + + if (canvas.offsetWidth > 100 || retries === 0) { + initSVMTraining(); + } else { + setTimeout(() => initSVMTrainingWithRetry(retries - 1), 500); + } +} + +function initSVMKernelWithRetry(retries = 3) { + const canvas = document.getElementById('svm-kernel-canvas'); + if (!canvas) return; + + if (canvas.offsetWidth > 100 || retries === 0) { + initSVMKernel(); + } else { + setTimeout(() => initSVMKernelWithRetry(retries - 1), 500); + } } + function initSVMBasic() { const canvas = document.getElementById('svm-basic-canvas'); - if (!canvas || canvas.dataset.initialized) return; - canvas.dataset.initialized = 'true'; - + if (!canvas) return; + + // Visibility handled by helper + + + if (canvas.dataset.initialized === 'true' && canvas.classList.contains('setup-done')) return; + canvas.classList.add('setup-done'); + const w1Slider = document.getElementById('svm-w1-slider'); const w2Slider = document.getElementById('svm-w2-slider'); const bSlider = document.getElementById('svm-b-slider'); - + if (w1Slider) { w1Slider.addEventListener('input', (e) => { state.svm.w1 = parseFloat(e.target.value); @@ -705,7 +845,7 @@ function initSVMBasic() { drawSVMBasic(); }); } - + if (w2Slider) { w2Slider.addEventListener('input', (e) => { state.svm.w2 = parseFloat(e.target.value); @@ -713,7 +853,7 @@ function initSVMBasic() { drawSVMBasic(); }); } - + if (bSlider) { bSlider.addEventListener('input', (e) => { state.svm.b = parseFloat(e.target.value); @@ -721,7 +861,7 @@ function initSVMBasic() { drawSVMBasic(); }); } - + drawSVMBasic(); } @@ -731,48 +871,48 @@ function drawSVMBasic() { logViz('SVM', 'Basic Decision Boundary', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); if (!ctx) { console.warn('Could not get canvas context for svm-basic-canvas'); return; } - + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 450; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; - + const xMin = 0, xMax = 10; const yMin = 0, yMax = 10; - + const scaleX = (x) => padding + ((x - xMin) / (xMax - xMin)) * chartWidth; const scaleY = (y) => height - padding - ((y - yMin) / (yMax - yMin)) * chartHeight; - + // Draw grid ctx.strokeStyle = 'rgba(42, 53, 68, 0.5)'; ctx.lineWidth = 1; for (let i = 0; i <= 10; i++) { const x = scaleX(i); const y = scaleY(i); - + ctx.beginPath(); ctx.moveTo(x, padding); ctx.lineTo(x, height - padding); ctx.stroke(); - + ctx.beginPath(); ctx.moveTo(padding, y); ctx.lineTo(width - padding, y); ctx.stroke(); } - + // Draw axes ctx.strokeStyle = '#2a3544'; ctx.lineWidth = 2; @@ -781,67 +921,67 @@ function drawSVMBasic() { ctx.lineTo(padding, height - padding); ctx.lineTo(width - padding, height - padding); ctx.stroke(); - + // Draw decision boundary const w1 = state.svm.w1; const w2 = state.svm.w2; const b = state.svm.b; - + if (Math.abs(w2) > 0.01) { ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 3; ctx.beginPath(); - + const x1 = xMin; const y1 = -(w1 * x1 + b) / w2; const x2 = xMax; const y2 = -(w1 * x2 + b) / w2; - + ctx.moveTo(scaleX(x1), scaleY(y1)); ctx.lineTo(scaleX(x2), scaleY(y2)); ctx.stroke(); } - + // Draw data points data.svm.forEach(point => { const x = scaleX(point.x1); const y = scaleY(point.x2); const score = w1 * point.x1 + w2 * point.x2 + b; - + ctx.fillStyle = point.class === 1 ? '#7ef0d4' : '#ff8c6a'; ctx.beginPath(); ctx.arc(x, y, 8, 0, 2 * Math.PI); ctx.fill(); - + ctx.strokeStyle = '#1a2332'; ctx.lineWidth = 2; ctx.stroke(); - + // Label ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 12px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(point.label, x, y - 15); - + // Score ctx.font = '11px monospace'; ctx.fillStyle = '#a9b4c2'; ctx.fillText(score.toFixed(2), x, y + 20); }); - + // Labels ctx.fillStyle = '#a9b4c2'; ctx.font = '13px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('X₁', width / 2, height - 20); - + logViz('SVM', 'Basic Decision Boundary', 'success'); ctx.save(); ctx.translate(20, height / 2); ctx.rotate(-Math.PI / 2); ctx.fillText('X₂', 0, 0); ctx.restore(); - + // Equation ctx.fillStyle = '#7ef0d4'; ctx.font = '14px monospace'; @@ -851,8 +991,8 @@ function drawSVMBasic() { function initSVMMargin() { const canvas = document.getElementById('svm-margin-canvas'); - if (!canvas || canvas.dataset.initialized) return; - canvas.dataset.initialized = 'true'; + if (canvas.dataset.initialized === 'true' && canvas.classList.contains('setup-done')) return; + canvas.classList.add('setup-done'); drawSVMMargin(); } @@ -862,33 +1002,33 @@ function drawSVMMargin() { logViz('SVM', 'Margin Visualization', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); if (!ctx) { console.warn('Could not get canvas context for svm-margin-canvas'); return; } - + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 450; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; - + const xMin = 0, xMax = 10; const yMin = 0, yMax = 10; - + const scaleX = (x) => padding + ((x - xMin) / (xMax - xMin)) * chartWidth; const scaleY = (y) => height - padding - ((y - yMin) / (yMax - yMin)) * chartHeight; - + // Use good values for visualization const w1 = 0.5, w2 = -1, b = 5.5; - + // Draw margin lines if (Math.abs(w2) > 0.01) { // Positive margin line @@ -901,7 +1041,7 @@ function drawSVMMargin() { ctx.moveTo(scaleX(x1), scaleY(y1)); ctx.lineTo(scaleX(x2), scaleY(y2)); ctx.stroke(); - + // Negative margin line ctx.beginPath(); y1 = -(w1 * x1 + b + 1) / w2; @@ -909,7 +1049,7 @@ function drawSVMMargin() { ctx.moveTo(scaleX(x1), scaleY(y1)); ctx.lineTo(scaleX(x2), scaleY(y2)); ctx.stroke(); - + // Decision boundary ctx.setLineDash([]); ctx.strokeStyle = '#6aa9ff'; @@ -921,19 +1061,19 @@ function drawSVMMargin() { ctx.lineTo(scaleX(x2), scaleY(y2)); ctx.stroke(); } - + // Draw data points data.svm.forEach(point => { const x = scaleX(point.x1); const y = scaleY(point.x2); const score = w1 * point.x1 + w2 * point.x2 + b; const isSupport = Math.abs(Math.abs(score) - 1) < 0.5; - + ctx.fillStyle = point.class === 1 ? '#7ef0d4' : '#ff8c6a'; ctx.beginPath(); ctx.arc(x, y, 8, 0, 2 * Math.PI); ctx.fill(); - + // Highlight support vectors if (isSupport) { ctx.strokeStyle = '#7ef0d4'; @@ -942,17 +1082,17 @@ function drawSVMMargin() { ctx.arc(x, y, 14, 0, 2 * Math.PI); ctx.stroke(); } - + ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 12px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(point.label, x, y - 20); }); - + // Show margin width const wNorm = Math.sqrt(w1 * w1 + w2 * w2); const marginWidth = 2 / wNorm; - + ctx.fillStyle = '#7ef0d4'; ctx.font = '16px sans-serif'; ctx.textAlign = 'left'; @@ -962,9 +1102,9 @@ function drawSVMMargin() { function initSVMCParameter() { const canvas = document.getElementById('svm-c-canvas'); - if (!canvas || canvas.dataset.initialized) return; - canvas.dataset.initialized = 'true'; - + if (canvas.dataset.initialized === 'true' && canvas.classList.contains('setup-done')) return; + canvas.classList.add('setup-done'); + const cSlider = document.getElementById('svm-c-slider'); if (cSlider) { cSlider.addEventListener('input', (e) => { @@ -974,7 +1114,7 @@ function initSVMCParameter() { drawSVMCParameter(); }); } - + drawSVMCParameter(); } @@ -986,42 +1126,42 @@ function drawSVMCParameter() { logViz('SVM', 'C Parameter Effect', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); if (!ctx) { console.warn('Could not get canvas context for svm-c-canvas'); return; } - + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 450; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; - + const xMin = 0, xMax = 10; const yMin = 0, yMax = 10; - + const scaleX = (x) => padding + ((x - xMin) / (xMax - xMin)) * chartWidth; const scaleY = (y) => height - padding - ((y - yMin) / (yMax - yMin)) * chartHeight; - + // Adjust margin based on C const C = state.svm.C; const marginFactor = Math.min(1, 10 / C); const w1 = 0.5 * marginFactor, w2 = -1 * marginFactor, b = 5.5; - + // Calculate violations let violations = 0; data.svm.forEach(point => { const score = w1 * point.x1 + w2 * point.x2 + b; if (point.class * score < 1) violations++; }); - + // Draw margin lines if (Math.abs(w2) > 0.01) { ctx.strokeStyle = '#ff8c6a'; @@ -1033,14 +1173,14 @@ function drawSVMCParameter() { ctx.moveTo(scaleX(x1), scaleY(y1)); ctx.lineTo(scaleX(x2), scaleY(y2)); ctx.stroke(); - + ctx.beginPath(); y1 = -(w1 * x1 + b + 1) / w2; y2 = -(w1 * x2 + b + 1) / w2; ctx.moveTo(scaleX(x1), scaleY(y1)); ctx.lineTo(scaleX(x2), scaleY(y2)); ctx.stroke(); - + ctx.setLineDash([]); ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 3; @@ -1051,26 +1191,26 @@ function drawSVMCParameter() { ctx.lineTo(scaleX(x2), scaleY(y2)); ctx.stroke(); } - + // Draw points data.svm.forEach(point => { const x = scaleX(point.x1); const y = scaleY(point.x2); const score = w1 * point.x1 + w2 * point.x2 + b; const violates = point.class * score < 1; - + ctx.fillStyle = point.class === 1 ? '#7ef0d4' : '#ff8c6a'; ctx.beginPath(); ctx.arc(x, y, 8, 0, 2 * Math.PI); ctx.fill(); - + if (violates) { ctx.strokeStyle = '#ff4444'; ctx.lineWidth = 3; ctx.stroke(); } }); - + // Update info const wNorm = Math.sqrt(w1 * w1 + w2 * w2); const marginWidth = 2 / wNorm; @@ -1078,19 +1218,19 @@ function drawSVMCParameter() { const violEl = document.getElementById('violations-count'); if (marginEl) marginEl.textContent = marginWidth.toFixed(2); if (violEl) violEl.textContent = violations; - + logViz('SVM', 'Margin Visualization', 'success'); } function initSVMTraining() { const canvas = document.getElementById('svm-train-canvas'); - if (!canvas || canvas.dataset.initialized) return; - canvas.dataset.initialized = 'true'; - + if (canvas.dataset.initialized === 'true' && canvas.classList.contains('setup-done')) return; + canvas.classList.add('setup-done'); + const trainBtn = document.getElementById('svm-train-btn'); const stepBtn = document.getElementById('svm-step-btn'); const resetBtn = document.getElementById('svm-reset-btn'); - + if (trainBtn) { trainBtn.addEventListener('click', () => { state.svm.training.step = 0; @@ -1100,7 +1240,7 @@ function initSVMTraining() { autoTrain(); }); } - + if (stepBtn) { stepBtn.addEventListener('click', () => { if (state.svm.training.step < data.svm.length) { @@ -1108,7 +1248,7 @@ function initSVMTraining() { } }); } - + if (resetBtn) { resetBtn.addEventListener('click', () => { state.svm.training.step = 0; @@ -1119,22 +1259,22 @@ function initSVMTraining() { drawSVMTraining(); }); } - + drawSVMTraining(); } function trainStep() { if (state.svm.training.step >= data.svm.length) return; - + const point = data.svm[state.svm.training.step]; const w = state.svm.training.w; const b = state.svm.training.b; const lr = state.svm.training.learningRate; const C = 1; - + const score = w[0] * point.x1 + w[1] * point.x2 + b; const violation = point.class * score < 1; - + if (violation) { w[0] = w[0] - lr * (w[0] - C * point.class * point.x1); w[1] = w[1] - lr * (w[1] - C * point.class * point.x2); @@ -1143,7 +1283,7 @@ function trainStep() { w[0] = w[0] - lr * w[0]; w[1] = w[1] - lr * w[1]; } - + state.svm.training.step++; updateTrainingInfo(point, violation); drawSVMTraining(); @@ -1151,7 +1291,7 @@ function trainStep() { function autoTrain() { if (!state.svm.training.isTraining) return; - + if (state.svm.training.step < data.svm.length) { trainStep(); setTimeout(autoTrain, 800); @@ -1174,33 +1314,33 @@ function drawSVMTraining() { logViz('SVM', 'Training Animation', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); if (!ctx) { console.warn('Could not get canvas context for svm-train-canvas'); return; } - + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 450; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; - + const xMin = 0, xMax = 10; const yMin = 0, yMax = 10; - + const scaleX = (x) => padding + ((x - xMin) / (xMax - xMin)) * chartWidth; const scaleY = (y) => height - padding - ((y - yMin) / (yMax - yMin)) * chartHeight; - + const w = state.svm.training.w; const b = state.svm.training.b; - + // Draw boundary if weights are non-zero if (Math.abs(w[1]) > 0.01) { ctx.strokeStyle = '#6aa9ff'; @@ -1212,20 +1352,20 @@ function drawSVMTraining() { ctx.lineTo(scaleX(x2), scaleY(y2)); ctx.stroke(); } - + // Draw points data.svm.forEach((point, i) => { const x = scaleX(point.x1); const y = scaleY(point.x2); const processed = i < state.svm.training.step; const current = i === state.svm.training.step - 1; - + ctx.fillStyle = point.class === 1 ? '#7ef0d4' : '#ff8c6a'; ctx.globalAlpha = processed ? 1 : 0.3; ctx.beginPath(); ctx.arc(x, y, 8, 0, 2 * Math.PI); ctx.fill(); - + if (current) { ctx.globalAlpha = 1; ctx.strokeStyle = '#ffff00'; @@ -1234,14 +1374,14 @@ function drawSVMTraining() { ctx.arc(x, y, 14, 0, 2 * Math.PI); ctx.stroke(); } - + ctx.globalAlpha = 1; ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 12px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(point.label, x, y - 15); }); - + logViz('SVM', 'Training Animation', 'success'); } @@ -1249,9 +1389,9 @@ let svmKernelChart = null; function initSVMKernel() { const canvas = document.getElementById('svm-kernel-canvas'); - if (!canvas || canvas.dataset.initialized) return; - canvas.dataset.initialized = 'true'; - + if (canvas.dataset.initialized === 'true' && canvas.classList.contains('setup-done')) return; + canvas.classList.add('setup-done'); + const kernelRadios = document.querySelectorAll('input[name="kernel"]'); kernelRadios.forEach(radio => { radio.addEventListener('change', (e) => { @@ -1263,7 +1403,7 @@ function initSVMKernel() { drawSVMKernel(); }); }); - + const paramSlider = document.getElementById('kernel-param-slider'); if (paramSlider) { paramSlider.addEventListener('input', (e) => { @@ -1273,7 +1413,7 @@ function initSVMKernel() { drawSVMKernel(); }); } - + drawSVMKernel(); } @@ -1283,42 +1423,42 @@ function drawSVMKernel() { logViz('SVM', 'Kernel Comparison', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 500; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; - + // Generate circular data const innerPoints = []; const outerPoints = []; - + for (let i = 0; i < 15; i++) { const angle = (i / 15) * 2 * Math.PI; innerPoints.push({ x: 5 + 1.5 * Math.cos(angle), y: 5 + 1.5 * Math.sin(angle), class: 1 }); } - + for (let i = 0; i < 20; i++) { const angle = (i / 20) * 2 * Math.PI; const r = 3.5 + Math.random() * 0.5; outerPoints.push({ x: 5 + r * Math.cos(angle), y: 5 + r * Math.sin(angle), class: -1 }); } - + const allPoints = [...innerPoints, ...outerPoints]; - + const xMin = 0, xMax = 10; const yMin = 0, yMax = 10; - + const scaleX = (x) => padding + ((x - xMin) / (xMax - xMin)) * chartWidth; const scaleY = (y) => height - padding - ((y - yMin) / (yMax - yMin)) * chartHeight; - + // Draw decision boundary based on kernel if (state.svm.kernel === 'linear') { // Linear can't separate circular data well @@ -1337,26 +1477,26 @@ function drawSVMKernel() { ctx.arc(scaleX(5), scaleY(5), radius * (chartWidth / 10), 0, 2 * Math.PI); ctx.stroke(); } - + // Draw points allPoints.forEach(point => { const x = scaleX(point.x); const y = scaleY(point.y); - + ctx.fillStyle = point.class === 1 ? '#7ef0d4' : '#ff8c6a'; ctx.beginPath(); ctx.arc(x, y, 5, 0, 2 * Math.PI); ctx.fill(); }); - + // Draw kernel info ctx.fillStyle = '#7ef0d4'; ctx.font = '16px sans-serif'; ctx.textAlign = 'left'; const kernelName = state.svm.kernel === 'linear' ? 'Linear Kernel' : - state.svm.kernel === 'polynomial' ? 'Polynomial Kernel' : 'RBF Kernel'; + state.svm.kernel === 'polynomial' ? 'Polynomial Kernel' : 'RBF Kernel'; ctx.fillText(kernelName, padding + 10, padding + 25); - + if (state.svm.kernel === 'linear') { ctx.font = '13px sans-serif'; ctx.fillStyle = '#ff8c6a'; @@ -1366,7 +1506,7 @@ function drawSVMKernel() { ctx.fillStyle = '#7ef0d4'; ctx.fillText('✓ Non-linear kernel successfully separates the data', padding + 10, padding + 50); } - + logViz('SVM', 'Kernel Comparison', 'success'); } @@ -1379,6 +1519,12 @@ function initLogistic() { function initSigmoid() { const canvas = document.getElementById('sigmoid-canvas'); if (!canvas || canvas.dataset.initialized) return; + + if (canvas.offsetWidth === 0) { + setTimeout(initSigmoid, 100); + return; + } + canvas.dataset.initialized = 'true'; drawSigmoid(); } @@ -1389,23 +1535,23 @@ function drawSigmoid() { logViz('Logistic Regression', 'Sigmoid Curve', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 350; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; - + const zMin = -10, zMax = 10; const scaleX = (z) => padding + ((z - zMin) / (zMax - zMin)) * chartWidth; const scaleY = (sig) => height - padding - sig * chartHeight; - + // Draw grid ctx.strokeStyle = 'rgba(42, 53, 68, 0.5)'; ctx.lineWidth = 1; @@ -1415,14 +1561,14 @@ function drawSigmoid() { ctx.moveTo(x, padding); ctx.lineTo(x, height - padding); ctx.stroke(); - + const y = padding + (chartHeight / 10) * i; ctx.beginPath(); ctx.moveTo(padding, y); ctx.lineTo(width - padding, y); ctx.stroke(); } - + // Draw axes ctx.strokeStyle = '#2a3544'; ctx.lineWidth = 2; @@ -1431,7 +1577,7 @@ function drawSigmoid() { ctx.lineTo(padding, height - padding); ctx.lineTo(width - padding, height - padding); ctx.stroke(); - + // Draw threshold line at 0.5 ctx.strokeStyle = '#ff8c6a'; ctx.lineWidth = 1; @@ -1441,7 +1587,7 @@ function drawSigmoid() { ctx.lineTo(width - padding, scaleY(0.5)); ctx.stroke(); ctx.setLineDash([]); - + // Draw sigmoid curve ctx.strokeStyle = '#7ef0d4'; ctx.lineWidth = 3; @@ -1454,7 +1600,7 @@ function drawSigmoid() { else ctx.lineTo(x, y); } ctx.stroke(); - + // Labels ctx.fillStyle = '#a9b4c2'; ctx.font = '12px sans-serif'; @@ -1465,20 +1611,26 @@ function drawSigmoid() { ctx.rotate(-Math.PI / 2); ctx.fillText('σ(z) probability', 0, 0); ctx.restore(); - + // Annotations ctx.fillStyle = '#7ef0d4'; ctx.textAlign = 'left'; ctx.fillText('σ(z) = 1/(1+e⁻ᶻ)', padding + 10, padding + 25); ctx.fillStyle = '#ff8c6a'; ctx.fillText('Threshold = 0.5', padding + 10, scaleY(0.5) - 10); - + logViz('Logistic Regression', 'Sigmoid Curve', 'success'); } function initLogisticClassification() { const canvas = document.getElementById('logistic-canvas'); if (!canvas || canvas.dataset.initialized) return; + + if (canvas.offsetWidth === 0) { + setTimeout(initLogisticClassification, 100); + return; + } + canvas.dataset.initialized = 'true'; drawLogisticClassification(); } @@ -1489,23 +1641,23 @@ function drawLogisticClassification() { logViz('Logistic Regression', 'Classification Boundary', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 400; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; - + const hMin = 140, hMax = 210; const scaleX = (h) => padding + ((h - hMin) / (hMax - hMin)) * chartWidth; const scaleY = (p) => height - padding - p * chartHeight; - + // Draw sigmoid curve ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 3; @@ -1519,7 +1671,7 @@ function drawLogisticClassification() { else ctx.lineTo(x, y); } ctx.stroke(); - + // Draw threshold line ctx.strokeStyle = '#ff8c6a'; ctx.setLineDash([5, 5]); @@ -1528,26 +1680,26 @@ function drawLogisticClassification() { ctx.lineTo(width - padding, scaleY(0.5)); ctx.stroke(); ctx.setLineDash([]); - + // Draw data points data.logistic.forEach(point => { const x = scaleX(point.height); const y = scaleY(point.prob); - + ctx.fillStyle = point.label === 1 ? '#7ef0d4' : '#ff8c6a'; ctx.beginPath(); ctx.arc(x, y, 6, 0, 2 * Math.PI); ctx.fill(); - + // Label ctx.fillStyle = '#e8eef6'; ctx.font = '11px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(point.height, x, height - padding + 20); }); - + logViz('Logistic Regression', 'Classification Boundary', 'success'); - + // Labels ctx.fillStyle = '#a9b4c2'; ctx.font = '12px sans-serif'; @@ -1566,8 +1718,14 @@ let knnState = { testPoint: { x: 2.5, y: 2.5 }, k: 3, distanceMetric: 'euclidean function initKNN() { const canvas = document.getElementById('knn-canvas'); if (!canvas || canvas.dataset.initialized) return; + + if (canvas.offsetWidth === 0) { + setTimeout(initKNN, 100); + return; + } + canvas.dataset.initialized = 'true'; - + const kSlider = document.getElementById('knn-k-slider'); if (kSlider) { kSlider.addEventListener('input', (e) => { @@ -1576,7 +1734,7 @@ function initKNN() { drawKNN(); }); } - + const distanceRadios = document.querySelectorAll('input[name="knn-distance"]'); distanceRadios.forEach(radio => { radio.addEventListener('change', (e) => { @@ -1584,11 +1742,11 @@ function initKNN() { drawKNN(); }); }); - + canvas.addEventListener('mousedown', startDragKNN); canvas.addEventListener('mousemove', dragKNN); canvas.addEventListener('mouseup', stopDragKNN); - + drawKNN(); } @@ -1597,14 +1755,14 @@ function startDragKNN(e) { const rect = canvas.getBoundingClientRect(); const mx = e.clientX - rect.left; const my = e.clientY - rect.top; - + const padding = 60; const chartWidth = canvas.width - 2 * padding; const chartHeight = canvas.height - 2 * padding; - + const tx = padding + (knnState.testPoint.x / 6) * chartWidth; const ty = canvas.height - padding - (knnState.testPoint.y / 6) * chartHeight; - + if (Math.abs(mx - tx) < 15 && Math.abs(my - ty) < 15) { knnState.dragging = true; } @@ -1612,19 +1770,19 @@ function startDragKNN(e) { function dragKNN(e) { if (!knnState.dragging) return; - + const canvas = document.getElementById('knn-canvas'); const rect = canvas.getBoundingClientRect(); const mx = e.clientX - rect.left; const my = e.clientY - rect.top; - + const padding = 60; const chartWidth = canvas.width - 2 * padding; const chartHeight = canvas.height - 2 * padding; - + knnState.testPoint.x = Math.max(0, Math.min(6, ((mx - padding) / chartWidth) * 6)); knnState.testPoint.y = Math.max(0, Math.min(6, ((canvas.height - padding - my) / chartHeight) * 6)); - + drawKNN(); } @@ -1638,22 +1796,22 @@ function drawKNN() { logViz('KNN', 'Draggable Point', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 450; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; - + const scaleX = (x) => padding + (x / 6) * chartWidth; const scaleY = (y) => height - padding - (y / 6) * chartHeight; - + // Calculate distances const distances = data.knn.map(point => { let d; @@ -1664,17 +1822,17 @@ function drawKNN() { } return { ...point, distance: d }; }); - + distances.sort((a, b) => a.distance - b.distance); const kNearest = distances.slice(0, knnState.k); - + // Count votes const votes = {}; kNearest.forEach(p => { votes[p.class] = (votes[p.class] || 0) + 1; }); const prediction = Object.keys(votes).reduce((a, b) => votes[a] > votes[b] ? a : b); - + // Draw lines to K nearest kNearest.forEach(point => { ctx.strokeStyle = 'rgba(126, 240, 212, 0.3)'; @@ -1684,19 +1842,19 @@ function drawKNN() { ctx.lineTo(scaleX(point.x), scaleY(point.y)); ctx.stroke(); }); - + // Draw training points distances.forEach(point => { const x = scaleX(point.x); const y = scaleY(point.y); const isNearest = kNearest.includes(point); - + ctx.fillStyle = point.class === 'orange' ? '#ff8c6a' : '#ffeb3b'; ctx.globalAlpha = isNearest ? 1 : 0.5; ctx.beginPath(); ctx.arc(x, y, 8, 0, 2 * Math.PI); ctx.fill(); - + if (isNearest) { ctx.strokeStyle = '#7ef0d4'; ctx.lineWidth = 2; @@ -1707,7 +1865,7 @@ function drawKNN() { } ctx.globalAlpha = 1; }); - + // Draw test point const tx = scaleX(knnState.testPoint.x); const ty = scaleY(knnState.testPoint.y); @@ -1718,14 +1876,14 @@ function drawKNN() { ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 3; ctx.stroke(); - + // Info ctx.fillStyle = '#7ef0d4'; ctx.font = '14px sans-serif'; ctx.textAlign = 'left'; ctx.fillText(`K=${knnState.k} | Prediction: ${prediction}`, padding + 10, padding + 25); ctx.fillText(`Votes: Orange=${votes.orange || 0}, Yellow=${votes.yellow || 0}`, padding + 10, padding + 50); - + logViz('KNN', 'Draggable Point', 'success'); } @@ -1739,6 +1897,12 @@ function initModelEvaluation() { function initConfusionMatrix() { const canvas = document.getElementById('confusion-canvas'); if (!canvas || canvas.dataset.initialized) return; + + if (canvas.offsetWidth === 0) { + setTimeout(initConfusionMatrix, 100); + return; + } + canvas.dataset.initialized = 'true'; drawConfusionMatrix(); } @@ -1749,22 +1913,22 @@ function drawConfusionMatrix() { logViz('Model Evaluation', 'Confusion Matrix', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 300; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const size = Math.min(width, height) - 100; const cellSize = size / 2; const startX = (width - size) / 2; const startY = 50; - + const cm = { tp: 600, fp: 100, fn: 300, tn: 900 }; - + // Draw cells const cells = [ { x: startX, y: startY, val: cm.tp, label: 'TP', color: '#7ef0d4' }, @@ -1772,14 +1936,14 @@ function drawConfusionMatrix() { { x: startX, y: startY + cellSize, val: cm.fp, label: 'FP', color: '#ff8c6a' }, { x: startX + cellSize, y: startY + cellSize, val: cm.tn, label: 'TN', color: '#7ef0d4' } ]; - + cells.forEach(cell => { ctx.fillStyle = cell.color + '22'; ctx.fillRect(cell.x, cell.y, cellSize, cellSize); ctx.strokeStyle = cell.color; ctx.lineWidth = 2; ctx.strokeRect(cell.x, cell.y, cellSize, cellSize); - + ctx.fillStyle = cell.color; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center'; @@ -1787,7 +1951,7 @@ function drawConfusionMatrix() { ctx.font = 'bold 32px sans-serif'; ctx.fillText(cell.val, cell.x + cellSize / 2, cell.y + cellSize / 2 + 25); }); - + // Labels ctx.fillStyle = '#a9b4c2'; ctx.font = '14px sans-serif'; @@ -1804,7 +1968,7 @@ function drawConfusionMatrix() { ctx.rotate(-Math.PI / 2); ctx.fillText('Actual Negative', 0, 0); ctx.restore(); - + logViz('Model Evaluation', 'Confusion Matrix', 'success'); } @@ -1813,8 +1977,14 @@ let rocState = { threshold: 0.5 }; function initROC() { const canvas = document.getElementById('roc-canvas'); if (!canvas || canvas.dataset.initialized) return; + + if (canvas.offsetWidth === 0) { + setTimeout(initROC, 100); + return; + } + canvas.dataset.initialized = 'true'; - + const slider = document.getElementById('roc-threshold-slider'); if (slider) { slider.addEventListener('input', (e) => { @@ -1823,7 +1993,7 @@ function initROC() { drawROC(); }); } - + drawROC(); } @@ -1833,20 +2003,20 @@ function drawROC() { logViz('Model Evaluation', 'ROC Curve', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 450; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 60; const chartSize = Math.min(width - 2 * padding, height - 2 * padding); const chartX = (width - chartSize) / 2; const chartY = (height - chartSize) / 2; - + // Calculate ROC points const rocPoints = []; for (let t = 0; t <= 1; t += 0.1) { @@ -1862,7 +2032,7 @@ function drawROC() { const fpr = fp / (fp + tn) || 0; rocPoints.push({ t, tpr, fpr }); } - + // Current threshold point let tp = 0, fp = 0, tn = 0, fn = 0; data.roc.forEach(e => { @@ -1874,7 +2044,7 @@ function drawROC() { }); const tpr = tp / (tp + fn) || 0; const fpr = fp / (fp + tn) || 0; - + // Draw diagonal (random) ctx.strokeStyle = 'rgba(255, 140, 106, 0.5)'; ctx.lineWidth = 2; @@ -1884,7 +2054,7 @@ function drawROC() { ctx.lineTo(chartX + chartSize, chartY); ctx.stroke(); ctx.setLineDash([]); - + // Draw ROC curve ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 3; @@ -1896,7 +2066,7 @@ function drawROC() { else ctx.lineTo(x, y); }); ctx.stroke(); - + // Draw current point const cx = chartX + fpr * chartSize; const cy = chartY + chartSize - tpr * chartSize; @@ -1904,14 +2074,14 @@ function drawROC() { ctx.beginPath(); ctx.arc(cx, cy, 8, 0, 2 * Math.PI); ctx.fill(); - + // Draw axes ctx.strokeStyle = '#2a3544'; ctx.lineWidth = 2; ctx.beginPath(); ctx.rect(chartX, chartY, chartSize, chartSize); ctx.stroke(); - + // Labels ctx.fillStyle = '#a9b4c2'; ctx.font = '12px sans-serif'; @@ -1922,20 +2092,26 @@ function drawROC() { ctx.rotate(-Math.PI / 2); ctx.fillText('TPR (True Positive Rate)', 0, 0); ctx.restore(); - + // Info ctx.fillStyle = '#7ef0d4'; ctx.font = '14px sans-serif'; ctx.textAlign = 'left'; ctx.fillText(`TPR: ${tpr.toFixed(2)} | FPR: ${fpr.toFixed(2)}`, chartX + 10, chartY + 25); ctx.fillText(`TP=${tp} FP=${fp} TN=${tn} FN=${fn}`, chartX + 10, chartY + 50); - + logViz('Model Evaluation', 'ROC Curve', 'success'); } function initR2() { const canvas = document.getElementById('r2-canvas'); if (!canvas || canvas.dataset.initialized) return; + + if (canvas.offsetWidth === 0) { + setTimeout(initR2, 100); + return; + } + canvas.dataset.initialized = 'true'; drawR2(); } @@ -1946,15 +2122,15 @@ function drawR2() { logViz('Model Evaluation', 'R² Score', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 350; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + // Dummy R² data const r2data = [ { x: 150, y: 50, pred: 52 }, @@ -1963,18 +2139,18 @@ function drawR2() { { x: 180, y: 80, pred: 78 }, { x: 190, y: 90, pred: 87 } ]; - + const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; - + const xMin = 140, xMax = 200, yMin = 40, yMax = 100; const scaleX = (x) => padding + ((x - xMin) / (xMax - xMin)) * chartWidth; const scaleY = (y) => height - padding - ((y - yMin) / (yMax - yMin)) * chartHeight; - + // Mean const mean = r2data.reduce((sum, p) => sum + p.y, 0) / r2data.length; - + // Draw mean line ctx.strokeStyle = '#ff8c6a'; ctx.setLineDash([5, 5]); @@ -1984,7 +2160,7 @@ function drawR2() { ctx.lineTo(width - padding, scaleY(mean)); ctx.stroke(); ctx.setLineDash([]); - + // Draw regression line ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 2; @@ -1992,7 +2168,7 @@ function drawR2() { ctx.moveTo(scaleX(xMin), scaleY(40)); ctx.lineTo(scaleX(xMax), scaleY(95)); ctx.stroke(); - + // Draw points r2data.forEach(p => { // Residual line @@ -2002,14 +2178,14 @@ function drawR2() { ctx.moveTo(scaleX(p.x), scaleY(p.y)); ctx.lineTo(scaleX(p.x), scaleY(p.pred)); ctx.stroke(); - + // Actual point ctx.fillStyle = '#7ef0d4'; ctx.beginPath(); ctx.arc(scaleX(p.x), scaleY(p.y), 6, 0, 2 * Math.PI); ctx.fill(); }); - + // Calculate R² let ssRes = 0, ssTot = 0; r2data.forEach(p => { @@ -2017,14 +2193,14 @@ function drawR2() { ssTot += Math.pow(p.y - mean, 2); }); const r2 = 1 - (ssRes / ssTot); - + // Info ctx.fillStyle = '#7ef0d4'; ctx.font = '16px sans-serif'; ctx.textAlign = 'left'; ctx.fillText(`R² = ${r2.toFixed(3)}`, padding + 10, padding + 25); ctx.fillText(`Model explains ${(r2 * 100).toFixed(1)}% of variance`, padding + 10, padding + 50); - + logViz('Model Evaluation', 'R² Score', 'success'); } @@ -2034,8 +2210,14 @@ let regState = { lambda: 0.1 }; function initRegularization() { const canvas = document.getElementById('regularization-canvas'); if (!canvas || canvas.dataset.initialized) return; + + if (canvas.offsetWidth === 0) { + setTimeout(initRegularization, 100); + return; + } + canvas.dataset.initialized = 'true'; - + const slider = document.getElementById('reg-lambda-slider'); if (slider) { slider.addEventListener('input', (e) => { @@ -2044,7 +2226,7 @@ function initRegularization() { drawRegularization(); }); } - + drawRegularization(); } @@ -2054,54 +2236,54 @@ function drawRegularization() { logViz('Regularization', 'L1 vs L2 Comparison', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 400; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; - + const features = ['x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9', 'x10']; const vanilla = [100, 200, 300, 50, 150, 250, 80, 120, 90, 180]; - + // Simulate L1 and L2 effects const l1 = vanilla.map(v => Math.abs(v) > 50 / regState.lambda ? v * (1 - regState.lambda * 0.5) : 0); const l2 = vanilla.map(v => v / (1 + regState.lambda)); - + const barWidth = chartWidth / (features.length * 3.5); const maxVal = Math.max(...vanilla); - + features.forEach((f, i) => { const x = padding + (i * chartWidth / features.length); - + // Vanilla const h1 = (vanilla[i] / maxVal) * chartHeight * 0.8; ctx.fillStyle = '#a9b4c2'; ctx.fillRect(x, height - padding - h1, barWidth, h1); - + // L1 const h2 = (l1[i] / maxVal) * chartHeight * 0.8; ctx.fillStyle = '#ff8c6a'; ctx.fillRect(x + barWidth * 1.2, height - padding - h2, barWidth, h2); - + // L2 const h3 = (l2[i] / maxVal) * chartHeight * 0.8; ctx.fillStyle = '#6aa9ff'; ctx.fillRect(x + barWidth * 2.4, height - padding - h3, barWidth, h3); - + // Feature label ctx.fillStyle = '#a9b4c2'; ctx.font = '11px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(f, x + barWidth * 1.5, height - padding + 20); }); - + // Legend const legendY = padding + 20; ctx.fillStyle = '#a9b4c2'; @@ -2110,17 +2292,17 @@ function drawRegularization() { ctx.font = '12px sans-serif'; ctx.textAlign = 'left'; ctx.fillText('Vanilla', padding + 30, legendY + 12); - + ctx.fillStyle = '#ff8c6a'; ctx.fillRect(padding + 100, legendY, 15, 15); ctx.fillStyle = '#e8eef6'; ctx.fillText('L1 (Lasso)', padding + 120, legendY + 12); - + ctx.fillStyle = '#6aa9ff'; ctx.fillRect(padding + 210, legendY, 15, 15); ctx.fillStyle = '#e8eef6'; ctx.fillText('L2 (Ridge)', padding + 230, legendY + 12); - + logViz('Regularization', 'L1 vs L2 Comparison', 'success'); } @@ -2128,9 +2310,15 @@ function drawRegularization() { function initBiasVariance() { const canvas = document.getElementById('bias-variance-canvas'); if (!canvas || canvas.dataset.initialized) return; + + if (canvas.offsetWidth === 0) { + setTimeout(initBiasVariance, 100); + return; + } + canvas.dataset.initialized = 'true'; drawBiasVariance(); - + const canvas2 = document.getElementById('complexity-canvas'); if (canvas2 && !canvas2.dataset.initialized) { canvas2.dataset.initialized = 'true'; @@ -2144,37 +2332,37 @@ function drawBiasVariance() { logViz('Bias-Variance', 'Three Models', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 400; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const sectionWidth = width / 3; const padding = 40; const chartHeight = height - 2 * padding; - + // Generate curved data const trueData = []; for (let x = 0; x <= 10; x += 0.5) { trueData.push({ x, y: 50 + 30 * Math.sin(x / 2) }); } - + // Draw three scenarios const scenarios = [ { title: 'High Bias\n(Underfit)', color: '#ff8c6a', degree: 1 }, { title: 'Good Fit', color: '#7ef0d4', degree: 2 }, { title: 'High Variance\n(Overfit)', color: '#ff8c6a', degree: 8 } ]; - + scenarios.forEach((scenario, idx) => { const offsetX = idx * sectionWidth; const scaleX = (x) => offsetX + padding + (x / 10) * (sectionWidth - 2 * padding); const scaleY = (y) => padding + chartHeight - ((y - 20) / 80) * chartHeight; - + // Draw true curve ctx.strokeStyle = 'rgba(106, 169, 255, 0.3)'; ctx.lineWidth = 2; @@ -2184,7 +2372,7 @@ function drawBiasVariance() { else ctx.lineTo(scaleX(p.x), scaleY(p.y)); }); ctx.stroke(); - + // Draw model fit ctx.strokeStyle = scenario.color; ctx.lineWidth = 3; @@ -2209,7 +2397,7 @@ function drawBiasVariance() { } } ctx.stroke(); - + // Title ctx.fillStyle = scenario.color; ctx.font = 'bold 14px sans-serif'; @@ -2219,7 +2407,7 @@ function drawBiasVariance() { ctx.fillText(line, offsetX + sectionWidth / 2, 20 + i * 18); }); }); - + logViz('Bias-Variance', 'Three Models', 'success'); } @@ -2229,22 +2417,22 @@ function drawComplexityCurve() { logViz('Bias-Variance', 'Complexity Curve', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 350; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; - + const scaleX = (x) => padding + (x / 10) * chartWidth; const scaleY = (y) => padding + chartHeight - (y / 100) * chartHeight; - + // Draw curves ctx.strokeStyle = '#ff8c6a'; ctx.lineWidth = 3; @@ -2255,7 +2443,7 @@ function drawComplexityCurve() { else ctx.lineTo(scaleX(x), scaleY(trainError)); } ctx.stroke(); - + ctx.strokeStyle = '#6aa9ff'; ctx.beginPath(); for (let x = 0; x <= 10; x += 0.1) { @@ -2264,13 +2452,13 @@ function drawComplexityCurve() { else ctx.lineTo(scaleX(x), scaleY(testError)); } ctx.stroke(); - + // Sweet spot ctx.fillStyle = '#7ef0d4'; ctx.beginPath(); ctx.arc(scaleX(5), scaleY(18), 8, 0, 2 * Math.PI); ctx.fill(); - + // Legend ctx.fillStyle = '#ff8c6a'; ctx.font = '12px sans-serif'; @@ -2280,9 +2468,9 @@ function drawComplexityCurve() { ctx.fillText('Test Error', padding + 10, padding + 40); ctx.fillStyle = '#7ef0d4'; ctx.fillText('● Sweet Spot', padding + 10, padding + 60); - + logViz('Bias-Variance', 'Complexity Curve', 'success'); - + // Labels ctx.fillStyle = '#a9b4c2'; ctx.textAlign = 'center'; @@ -2298,6 +2486,12 @@ function drawComplexityCurve() { function initCrossValidation() { const canvas = document.getElementById('cv-canvas'); if (!canvas || canvas.dataset.initialized) return; + + if (canvas.offsetWidth === 0) { + setTimeout(initCrossValidation, 100); + return; + } + canvas.dataset.initialized = 'true'; drawCrossValidation(); } @@ -2308,56 +2502,56 @@ function drawCrossValidation() { logViz('Cross-Validation', 'K-Fold Visualization', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 400; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const blockSize = 50; const gap = 10; const numBlocks = 12; const k = 3; const blocksPerFold = numBlocks / k; - + const startX = (width - (numBlocks * blockSize + (numBlocks - 1) * gap)) / 2; - + const folds = [0.96, 0.84, 0.90]; - + for (let fold = 0; fold < k; fold++) { const offsetY = 80 + fold * 120; - + // Fold label ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 14px sans-serif'; ctx.textAlign = 'right'; ctx.fillText(`Fold ${fold + 1}:`, startX - 20, offsetY + blockSize / 2 + 5); - + // Draw blocks for (let i = 0; i < numBlocks; i++) { const x = startX + i * (blockSize + gap); const isFold = i >= fold * blocksPerFold && i < (fold + 1) * blocksPerFold; - + ctx.fillStyle = isFold ? '#6aa9ff' : '#7ef0d4'; ctx.fillRect(x, offsetY, blockSize, blockSize); - + // Label ctx.fillStyle = '#1a2332'; ctx.font = 'bold 12px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(String.fromCharCode(65 + i), x + blockSize / 2, offsetY + blockSize / 2 + 5); } - + // Accuracy ctx.fillStyle = '#7ef0d4'; ctx.font = '14px sans-serif'; ctx.textAlign = 'left'; ctx.fillText(`Acc: ${folds[fold].toFixed(2)}`, startX + numBlocks * (blockSize + gap) + 20, offsetY + blockSize / 2 + 5); } - + // Legend ctx.fillStyle = '#6aa9ff'; ctx.fillRect(startX, 30, 30, 20); @@ -2365,34 +2559,42 @@ function drawCrossValidation() { ctx.font = '12px sans-serif'; ctx.textAlign = 'left'; ctx.fillText('Test Set', startX + 40, 45); - + ctx.fillStyle = '#7ef0d4'; ctx.fillRect(startX + 120, 30, 30, 20); ctx.fillText('Training Set', startX + 160, 45); - + // Final result const mean = folds.reduce((a, b) => a + b) / folds.length; const std = Math.sqrt(folds.reduce((sum, x) => sum + Math.pow(x - mean, 2), 0) / folds.length); - + ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(`Final Score: ${mean.toFixed(2)} ± ${std.toFixed(3)}`, width / 2, height - 20); - + logViz('Cross-Validation', 'K-Fold Visualization', 'success'); } // Preprocessing function initPreprocessing() { - const canvas = document.getElementById('scaling-canvas'); - if (canvas && !canvas.dataset.initialized) { - canvas.dataset.initialized = 'true'; + const c1 = document.getElementById('scaling-canvas'); + if (c1 && !c1.dataset.initialized) { + if (c1.offsetWidth === 0) { + setTimeout(initPreprocessing, 100); + return; + } + c1.dataset.initialized = 'true'; drawScaling(); } - - const canvas2 = document.getElementById('pipeline-canvas'); - if (canvas2 && !canvas2.dataset.initialized) { - canvas2.dataset.initialized = 'true'; + + const c2 = document.getElementById('pipeline-canvas'); + if (c2 && !c2.dataset.initialized) { + if (c2.offsetWidth === 0) { + setTimeout(initPreprocessing, 100); + return; + } + c2.dataset.initialized = 'true'; drawPipeline(); } } @@ -2403,58 +2605,58 @@ function drawScaling() { logViz('Preprocessing', 'Feature Scaling', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 350; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const before = [10, 20, 30, 40, 50]; const standard = [-1.26, -0.63, 0, 0.63, 1.26]; const minmax = [0, 0.25, 0.5, 0.75, 1.0]; - + const sectionWidth = width / 3; const padding = 40; const barWidth = 30; - + const datasets = [ { data: before, title: 'Original', maxVal: 60 }, { data: standard, title: 'StandardScaler', maxVal: 2 }, { data: minmax, title: 'MinMaxScaler', maxVal: 1.2 } ]; - + datasets.forEach((dataset, idx) => { const offsetX = idx * sectionWidth; const centerX = offsetX + sectionWidth / 2; - + // Title ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 14px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(dataset.title, centerX, 30); - + // Draw bars dataset.data.forEach((val, i) => { const barHeight = Math.abs(val) / dataset.maxVal * 200; const x = centerX - barWidth / 2; const y = val >= 0 ? 200 - barHeight : 200; - + ctx.fillStyle = '#6aa9ff'; ctx.fillRect(x, y, barWidth, barHeight); - + // Value label ctx.fillStyle = '#a9b4c2'; ctx.font = '10px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(val.toFixed(2), centerX, val >= 0 ? y - 5 : y + barHeight + 15); - + centerX += 35; }); }); - + logViz('Preprocessing', 'Feature Scaling', 'success'); } @@ -2464,29 +2666,29 @@ function drawPipeline() { logViz('Preprocessing', 'Pipeline Flow', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 300; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const steps = ['Raw Data', 'Handle Missing', 'Encode Categories', 'Scale Features', 'Train Model']; const stepWidth = (width - 100) / steps.length; const y = height / 2; - + steps.forEach((step, i) => { const x = 50 + i * stepWidth; - + // Box ctx.fillStyle = '#2a3544'; ctx.fillRect(x, y - 30, stepWidth - 40, 60); ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 2; ctx.strokeRect(x, y - 30, stepWidth - 40, 60); - + // Text ctx.fillStyle = '#e8eef6'; ctx.font = '12px sans-serif'; @@ -2495,7 +2697,7 @@ function drawPipeline() { words.forEach((word, j) => { ctx.fillText(word, x + (stepWidth - 40) / 2, y + j * 15 - 5); }); - + // Arrow if (i < steps.length - 1) { ctx.strokeStyle = '#7ef0d4'; @@ -2504,7 +2706,7 @@ function drawPipeline() { ctx.moveTo(x + stepWidth - 40, y); ctx.lineTo(x + stepWidth - 10, y); ctx.stroke(); - + // Arrowhead ctx.fillStyle = '#7ef0d4'; ctx.beginPath(); @@ -2514,18 +2716,23 @@ function drawPipeline() { ctx.fill(); } }); - + logViz('Preprocessing', 'Pipeline Flow', 'success'); } // Loss Functions function initLossFunctions() { const canvas = document.getElementById('loss-comparison-canvas'); - if (canvas && !canvas.dataset.initialized) { - canvas.dataset.initialized = 'true'; - drawLossComparison(); + if (!canvas || canvas.dataset.initialized) return; + + if (canvas.offsetWidth === 0) { + setTimeout(initLossFunctions, 100); + return; } - + + canvas.dataset.initialized = 'true'; + drawLossComparison(); + const canvas2 = document.getElementById('loss-curves-canvas'); if (canvas2 && !canvas2.dataset.initialized) { canvas2.dataset.initialized = 'true'; @@ -2539,18 +2746,18 @@ function drawLossComparison() { logViz('Loss Functions', 'Loss Comparison', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 400; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const actual = [10, 20, 30, 40, 50]; const predicted = [12, 19, 32, 38, 51]; - + // Calculate losses let mse = 0, mae = 0; actual.forEach((a, i) => { @@ -2561,47 +2768,47 @@ function drawLossComparison() { mse /= actual.length; mae /= actual.length; const rmse = Math.sqrt(mse); - + // Display const padding = 60; const barHeight = 60; const startY = 100; const maxWidth = width - 2 * padding; - + const losses = [ { name: 'MSE', value: mse, color: '#ff8c6a' }, { name: 'MAE', value: mae, color: '#6aa9ff' }, { name: 'RMSE', value: rmse, color: '#7ef0d4' } ]; - + const maxLoss = Math.max(...losses.map(l => l.value)); - + losses.forEach((loss, i) => { const y = startY + i * (barHeight + 30); const barWidth = (loss.value / maxLoss) * maxWidth; - + // Bar ctx.fillStyle = loss.color; ctx.fillRect(padding, y, barWidth, barHeight); - + // Label ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 14px sans-serif'; ctx.textAlign = 'left'; ctx.fillText(loss.name, padding + 10, y + barHeight / 2 + 5); - + // Value ctx.font = '16px sans-serif'; ctx.textAlign = 'right'; ctx.fillText(loss.value.toFixed(2), padding + barWidth - 10, y + barHeight / 2 + 5); }); - + // Title ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('Regression Loss Comparison', width / 2, 50); - + logViz('Loss Functions', 'Loss Comparison', 'success'); } @@ -2611,22 +2818,22 @@ function drawLossCurves() { logViz('Loss Functions', 'Loss Curves', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 350; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; - + const scaleX = (x) => padding + (x / 10) * chartWidth; const scaleY = (y) => height - padding - (y / 100) * chartHeight; - + // Draw MSE curve ctx.strokeStyle = '#ff8c6a'; ctx.lineWidth = 3; @@ -2637,7 +2844,7 @@ function drawLossCurves() { else ctx.lineTo(scaleX(x + 10), scaleY(y)); } ctx.stroke(); - + // Draw MAE curve ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 3; @@ -2648,7 +2855,7 @@ function drawLossCurves() { else ctx.lineTo(scaleX(x + 10), scaleY(y)); } ctx.stroke(); - + // Legend ctx.fillStyle = '#ff8c6a'; ctx.font = '12px sans-serif'; @@ -2656,7 +2863,7 @@ function drawLossCurves() { ctx.fillText('MSE (quadratic penalty)', padding + 10, padding + 20); ctx.fillStyle = '#6aa9ff'; ctx.fillText('MAE (linear penalty)', padding + 10, padding + 40); - + // Labels ctx.fillStyle = '#a9b4c2'; ctx.textAlign = 'center'; @@ -2666,7 +2873,7 @@ function drawLossCurves() { ctx.rotate(-Math.PI / 2); ctx.fillText('Loss', 0, 0); ctx.restore(); - + logViz('Loss Functions', 'Loss Curves', 'success'); } @@ -2676,11 +2883,16 @@ let cvKChart = null; function initOptimalK() { const canvas1 = document.getElementById('elbow-canvas'); - if (canvas1 && !canvas1.dataset.initialized) { - canvas1.dataset.initialized = 'true'; - drawElbowCurve(); + if (!canvas1 || canvas1.dataset.initialized) return; + + if (canvas1.offsetWidth === 0) { + setTimeout(initOptimalK, 100); + return; } - + + canvas1.dataset.initialized = 'true'; + drawElbowCurve(); + const canvas2 = document.getElementById('cv-k-canvas'); if (canvas2 && !canvas2.dataset.initialized) { canvas2.dataset.initialized = 'true'; @@ -2691,27 +2903,27 @@ function initOptimalK() { function drawElbowCurve() { const canvas = document.getElementById('elbow-canvas'); if (!canvas) return; - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 400; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; - + // Data from application_data_json const kValues = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]; const accuracies = [0.96, 0.94, 0.93, 0.91, 0.89, 0.87, 0.85, 0.84, 0.83, 0.82, 0.81, 0.80, 0.79, 0.78, 0.77, 0.76, 0.75, 0.74, 0.73]; const optimalK = 3; - + const scaleX = (k) => padding + ((k - 1) / (kValues.length - 1)) * chartWidth; const scaleY = (acc) => height - padding - ((acc - 0.7) / 0.3) * chartHeight; - + // Draw axes ctx.strokeStyle = '#2a3544'; ctx.lineWidth = 2; @@ -2720,18 +2932,18 @@ function drawElbowCurve() { ctx.lineTo(padding, height - padding); ctx.lineTo(width - padding, height - padding); ctx.stroke(); - + // Destroy existing chart if (elbowChart) { elbowChart.destroy(); } - + // Use Chart.js // Destroy existing chart if (elbowChart) { elbowChart.destroy(); } - + elbowChart = createVerifiedVisualization('elbow-canvas', { type: 'line', data: { @@ -2800,36 +3012,36 @@ function drawCVKHeatmap() { logViz('Optimal K', 'CV Heatmap', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 400; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 80; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; - + const kValues = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]; const folds = ['Fold 1', 'Fold 2', 'Fold 3']; const fold1 = [0.98, 0.92, 0.88, 0.85, 0.83, 0.81, 0.79, 0.77, 0.75, 0.73]; const fold2 = [0.96, 0.91, 0.87, 0.83, 0.81, 0.79, 0.77, 0.75, 0.73, 0.71]; const fold3 = [0.94, 0.90, 0.86, 0.82, 0.79, 0.77, 0.75, 0.73, 0.71, 0.69]; const allData = [fold1, fold2, fold3]; - + const cellWidth = chartWidth / kValues.length; const cellHeight = chartHeight / folds.length; - + // Draw heatmap folds.forEach((fold, i) => { kValues.forEach((k, j) => { const acc = allData[i][j]; const x = padding + j * cellWidth; const y = padding + i * cellHeight; - + // Color based on accuracy const intensity = (acc - 0.65) / 0.35; const r = Math.floor(106 + (126 - 106) * intensity); @@ -2837,12 +3049,12 @@ function drawCVKHeatmap() { const b = Math.floor(255 + (212 - 255) * intensity); ctx.fillStyle = `rgb(${r}, ${g}, ${b})`; ctx.fillRect(x, y, cellWidth, cellHeight); - + // Border ctx.strokeStyle = '#1a2332'; ctx.lineWidth = 1; ctx.strokeRect(x, y, cellWidth, cellHeight); - + // Text ctx.fillStyle = '#1a2332'; ctx.font = 'bold 11px sans-serif'; @@ -2850,7 +3062,7 @@ function drawCVKHeatmap() { ctx.fillText(acc.toFixed(2), x + cellWidth / 2, y + cellHeight / 2 + 4); }); }); - + // Row labels ctx.fillStyle = '#e8eef6'; ctx.font = '12px sans-serif'; @@ -2859,14 +3071,14 @@ function drawCVKHeatmap() { const y = padding + i * cellHeight + cellHeight / 2; ctx.fillText(fold, padding - 10, y + 4); }); - + // Column labels ctx.textAlign = 'center'; kValues.forEach((k, j) => { const x = padding + j * cellWidth + cellWidth / 2; ctx.fillText(`K=${k}`, x, padding - 10); }); - + // Mean accuracy ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 14px sans-serif'; @@ -2878,7 +3090,7 @@ function drawCVKHeatmap() { const maxMean = Math.max(...meanAccs); const optIdx = meanAccs.indexOf(maxMean); ctx.fillText(`Best K = ${kValues[optIdx]} (Mean Acc: ${maxMean.toFixed(3)})`, padding, height - 20); - + logViz('Optimal K', 'CV Heatmap', 'success'); } @@ -2887,17 +3099,22 @@ let gridSearchChart = null; function initHyperparameterTuning() { const canvas1 = document.getElementById('gridsearch-heatmap'); - if (canvas1 && !canvas1.dataset.initialized) { - canvas1.dataset.initialized = 'true'; - drawGridSearchHeatmap(); + if (!canvas1 || canvas1.dataset.initialized) return; + + if (canvas1.offsetWidth === 0) { + setTimeout(initHyperparameterTuning, 100); + return; } - + + canvas1.dataset.initialized = 'true'; + drawGridSearchHeatmap(); + const canvas2 = document.getElementById('param-surface'); if (canvas2 && !canvas2.dataset.initialized) { canvas2.dataset.initialized = 'true'; drawParamSurface(); } - + const radios = document.querySelectorAll('input[name="grid-model"]'); radios.forEach(radio => { radio.addEventListener('change', () => { @@ -2912,22 +3129,22 @@ function drawGridSearchHeatmap() { logViz('Hyperparameter Tuning', 'GridSearch Heatmap', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 450; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 80; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; - + const cValues = [0.1, 1, 10, 100]; const gammaValues = [0.001, 0.01, 0.1, 1]; - + // Simulate accuracy grid const accuracies = [ [0.65, 0.82, 0.88, 0.75], @@ -2935,12 +3152,12 @@ function drawGridSearchHeatmap() { [0.85, 0.93, 0.92, 0.87], [0.80, 0.88, 0.84, 0.82] ]; - + const cellWidth = chartWidth / cValues.length; const cellHeight = chartHeight / gammaValues.length; - + let bestAcc = 0, bestI = 0, bestJ = 0; - + // Draw heatmap gammaValues.forEach((gamma, i) => { cValues.forEach((c, j) => { @@ -2950,10 +3167,10 @@ function drawGridSearchHeatmap() { bestI = i; bestJ = j; } - + const x = padding + j * cellWidth; const y = padding + i * cellHeight; - + // Color gradient const intensity = (acc - 0.6) / 0.35; const r = Math.floor(255 - 149 * intensity); @@ -2961,12 +3178,12 @@ function drawGridSearchHeatmap() { const b = Math.floor(106 + 106 * intensity); ctx.fillStyle = `rgb(${r}, ${g}, ${b})`; ctx.fillRect(x, y, cellWidth, cellHeight); - + // Border ctx.strokeStyle = '#1a2332'; ctx.lineWidth = 2; ctx.strokeRect(x, y, cellWidth, cellHeight); - + // Text ctx.fillStyle = '#1a2332'; ctx.font = 'bold 14px sans-serif'; @@ -2974,14 +3191,14 @@ function drawGridSearchHeatmap() { ctx.fillText(acc.toFixed(2), x + cellWidth / 2, y + cellHeight / 2 + 5); }); }); - + // Highlight best const bestX = padding + bestJ * cellWidth; const bestY = padding + bestI * cellHeight; ctx.strokeStyle = '#7ef0d4'; ctx.lineWidth = 4; ctx.strokeRect(bestX, bestY, cellWidth, cellHeight); - + // Labels ctx.fillStyle = '#e8eef6'; ctx.font = '12px sans-serif'; @@ -2990,13 +3207,13 @@ function drawGridSearchHeatmap() { const y = padding + i * cellHeight + cellHeight / 2; ctx.fillText(`γ=${gamma}`, padding - 10, y + 5); }); - + ctx.textAlign = 'center'; cValues.forEach((c, j) => { const x = padding + j * cellWidth + cellWidth / 2; ctx.fillText(`C=${c}`, x, padding - 10); }); - + // Axis labels ctx.fillStyle = '#a9b4c2'; ctx.font = 'bold 14px sans-serif'; @@ -3006,7 +3223,7 @@ function drawGridSearchHeatmap() { ctx.rotate(-Math.PI / 2); ctx.fillText('Gamma Parameter', 0, 0); ctx.restore(); - + // Best params - Use Chart.js for bar comparison instead const compareData = []; cValues.forEach((c, j) => { @@ -3019,17 +3236,17 @@ function drawGridSearchHeatmap() { }); }); }); - + // Sort and get top 5 compareData.sort((a, b) => b.acc - a.acc); const top5 = compareData.slice(0, 5); - + // Add annotation for best ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 14px sans-serif'; ctx.textAlign = 'left'; ctx.fillText(`Best: C=${cValues[bestJ]}, γ=${gammaValues[bestI]} → Acc=${bestAcc.toFixed(2)}`, padding, height - 30); - + logViz('Hyperparameter Tuning', 'GridSearch Heatmap', 'success'); } @@ -3039,23 +3256,23 @@ function drawParamSurface() { logViz('Hyperparameter Tuning', 'Parameter Surface', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 400; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 60; const centerX = width / 2; const centerY = height / 2; - + // Draw 3D-ish surface using contour lines const levels = [0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95]; const colors = ['#ff8c6a', '#ffa07a', '#ffb490', '#ffc8a6', '#7ef0d4', '#6aa9ff', '#5a99ef']; - + levels.forEach((level, i) => { const radius = 150 - i * 20; ctx.strokeStyle = colors[i]; @@ -3063,26 +3280,26 @@ function drawParamSurface() { ctx.beginPath(); ctx.ellipse(centerX, centerY, radius, radius * 0.6, 0, 0, 2 * Math.PI); ctx.stroke(); - + // Label ctx.fillStyle = colors[i]; ctx.font = '11px sans-serif'; ctx.textAlign = 'left'; ctx.fillText(level.toFixed(2), centerX + radius + 10, centerY); }); - + // Center point (optimum) ctx.fillStyle = '#7ef0d4'; ctx.beginPath(); ctx.arc(centerX, centerY, 8, 0, 2 * Math.PI); ctx.fill(); - + ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 14px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('Optimal Point', centerX, centerY - 20); ctx.fillText('(C=1, γ=scale)', centerX, centerY + 35); - + // Axis labels ctx.fillStyle = '#a9b4c2'; ctx.font = '12px sans-serif'; @@ -3092,12 +3309,12 @@ function drawParamSurface() { ctx.rotate(-Math.PI / 2); ctx.fillText('← Gamma', 0, 0); ctx.restore(); - + ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('Performance Surface (3D Contour View)', width / 2, 30); - + logViz('Hyperparameter Tuning', 'Parameter Surface', 'success'); } @@ -3107,29 +3324,11 @@ let categoricalNBChart = null; let gaussianNBChart = null; function initNaiveBayes() { - const canvas1 = document.getElementById('bayes-theorem-viz'); - if (canvas1 && !canvas1.dataset.initialized) { - canvas1.dataset.initialized = 'true'; - drawBayesTheorem(); - } - - const canvas2 = document.getElementById('spam-classification'); - if (canvas2 && !canvas2.dataset.initialized) { - canvas2.dataset.initialized = 'true'; - drawSpamClassification(); - } - - const canvas3 = document.getElementById('categorical-nb-canvas'); - if (canvas3 && !canvas3.dataset.initialized) { - canvas3.dataset.initialized = 'true'; - drawCategoricalNB(); - } - - const canvas4 = document.getElementById('gaussian-nb-canvas'); - if (canvas4 && !canvas4.dataset.initialized) { - canvas4.dataset.initialized = 'true'; - drawGaussianNB(); - } + ensureCanvasVisible('bayes-theorem-viz', drawBayesTheorem); + ensureCanvasVisible('spam-classification', drawSpamClassification); + ensureCanvasVisible('categorical-nb-canvas', drawCategoricalNB); + ensureCanvasVisible('gaussian-nb-canvas', drawGaussianNB); + ensureCanvasVisible('bayes-comparison-canvas', drawBayesComparison); } function drawBayesTheorem() { @@ -3138,18 +3337,18 @@ function drawBayesTheorem() { logViz('Naive Bayes', 'Bayes Theorem Flow', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 400; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const centerX = width / 2; const centerY = height / 2; - + // Draw formula components as boxes const boxes = [ { x: centerX - 300, y: centerY - 80, w: 120, h: 60, text: 'P(C|F)', label: 'Posterior', color: '#7ef0d4' }, @@ -3157,24 +3356,24 @@ function drawBayesTheorem() { { x: centerX + 100, y: centerY - 80, w: 100, h: 60, text: 'P(C)', label: 'Prior', color: '#ffb490' }, { x: centerX - 50, y: centerY + 60, w: 100, h: 60, text: 'P(F)', label: 'Evidence', color: '#ff8c6a' } ]; - + boxes.forEach(box => { ctx.fillStyle = box.color + '33'; ctx.fillRect(box.x, box.y, box.w, box.h); ctx.strokeStyle = box.color; ctx.lineWidth = 2; ctx.strokeRect(box.x, box.y, box.w, box.h); - + ctx.fillStyle = box.color; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(box.text, box.x + box.w / 2, box.y + box.h / 2); - + ctx.font = '12px sans-serif'; ctx.fillStyle = '#a9b4c2'; ctx.fillText(box.label, box.x + box.w / 2, box.y + box.h + 20); }); - + // Draw arrows and operators ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 20px sans-serif'; @@ -3182,7 +3381,7 @@ function drawBayesTheorem() { ctx.fillText('=', centerX - 160, centerY - 40); ctx.fillText('×', centerX + 40, centerY - 40); ctx.fillText('÷', centerX, centerY + 20); - + // Title ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 18px sans-serif'; @@ -3192,17 +3391,17 @@ function drawBayesTheorem() { function drawCategoricalNB() { const canvas = document.getElementById('categorical-nb-canvas'); if (!canvas) return; - + if (categoricalNBChart) { categoricalNBChart.destroy(); } - + const ctx = canvas.getContext('2d'); - + if (categoricalNBChart) { categoricalNBChart.destroy(); } - + categoricalNBChart = createVerifiedVisualization('categorical-nb-canvas', { type: 'bar', data: { @@ -3264,23 +3463,23 @@ function drawGaussianNB() { logViz('Naive Bayes', 'Gaussian NB Boundary', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 400; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; - + const xMin = 0, xMax = 5, yMin = 0, yMax = 4; const scaleX = (x) => padding + (x / xMax) * chartWidth; const scaleY = (y) => height - padding - (y / yMax) * chartHeight; - + // Draw decision boundary (approximate) ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 3; @@ -3290,9 +3489,9 @@ function drawGaussianNB() { ctx.lineTo(scaleX(2.5), scaleY(4)); ctx.stroke(); ctx.setLineDash([]); - + // Draw "Yes" points - const yesPoints = [{x: 1.0, y: 2.0}, {x: 2.0, y: 1.0}, {x: 1.5, y: 1.8}]; + const yesPoints = [{ x: 1.0, y: 2.0 }, { x: 2.0, y: 1.0 }, { x: 1.5, y: 1.8 }]; yesPoints.forEach(p => { ctx.fillStyle = '#7ef0d4'; ctx.beginPath(); @@ -3302,9 +3501,9 @@ function drawGaussianNB() { ctx.lineWidth = 2; ctx.stroke(); }); - + // Draw "No" points - const noPoints = [{x: 3.0, y: 3.0}, {x: 3.5, y: 2.8}, {x: 2.9, y: 3.2}]; + const noPoints = [{ x: 3.0, y: 3.0 }, { x: 3.5, y: 2.8 }, { x: 2.9, y: 3.2 }]; noPoints.forEach(p => { ctx.fillStyle = '#ff8c6a'; ctx.beginPath(); @@ -3314,7 +3513,7 @@ function drawGaussianNB() { ctx.lineWidth = 2; ctx.stroke(); }); - + // Draw test point ctx.fillStyle = '#ffeb3b'; ctx.beginPath(); @@ -3323,7 +3522,7 @@ function drawGaussianNB() { ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 3; ctx.stroke(); - + // Label test point ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 12px sans-serif'; @@ -3331,7 +3530,7 @@ function drawGaussianNB() { ctx.fillText('Test [2.0, 2.0]', scaleX(2.0), scaleY(2.0) - 20); ctx.fillStyle = '#7ef0d4'; ctx.fillText('→ YES', scaleX(2.0), scaleY(2.0) + 30); - + // Axes ctx.strokeStyle = '#2a3544'; ctx.lineWidth = 2; @@ -3340,7 +3539,7 @@ function drawGaussianNB() { ctx.lineTo(padding, height - padding); ctx.lineTo(width - padding, height - padding); ctx.stroke(); - + // Labels ctx.fillStyle = '#a9b4c2'; ctx.font = '12px sans-serif'; @@ -3351,7 +3550,7 @@ function drawGaussianNB() { ctx.rotate(-Math.PI / 2); ctx.fillText('X₂', 0, 0); ctx.restore(); - + // Legend ctx.fillStyle = '#7ef0d4'; ctx.beginPath(); @@ -3361,36 +3560,36 @@ function drawGaussianNB() { ctx.font = '11px sans-serif'; ctx.textAlign = 'left'; ctx.fillText('Class: Yes', padding + 30, 35); - + ctx.fillStyle = '#ff8c6a'; ctx.beginPath(); ctx.arc(padding + 120, 30, 6, 0, 2 * Math.PI); ctx.fill(); ctx.fillStyle = '#e8eef6'; ctx.fillText('Class: No', padding + 130, 35); - + ctx.fillStyle = '#6aa9ff'; ctx.fillText('| Decision Boundary', padding + 210, 35); - + logViz('Naive Bayes', 'Gaussian NB Boundary', 'success'); } function drawSpamClassification() { const canvas = document.getElementById('spam-classification'); if (!canvas) return; - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 400; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 40; const stepHeight = 70; const startY = 60; - + // Step 1: Features ctx.fillStyle = '#6aa9ff'; ctx.font = 'bold 14px sans-serif'; @@ -3399,7 +3598,7 @@ function drawSpamClassification() { ctx.fillStyle = '#e8eef6'; ctx.font = '13px sans-serif'; ctx.fillText('Words: ["free", "winner", "click"]', padding + 20, startY + 25); - + // Step 2: Calculate P(spam) const y2 = startY + stepHeight; ctx.fillStyle = '#6aa9ff'; @@ -3412,7 +3611,7 @@ function drawSpamClassification() { ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 14px monospace'; ctx.fillText('= 0.1008', padding + 20, y2 + 65); - + // Step 3: Calculate P(not spam) const y3 = y2 + stepHeight + 50; ctx.fillStyle = '#6aa9ff'; @@ -3425,7 +3624,7 @@ function drawSpamClassification() { ctx.fillStyle = '#ff8c6a'; ctx.font = 'bold 14px monospace'; ctx.fillText('= 0.0007', padding + 20, y3 + 65); - + // Step 4: Decision const y4 = y3 + stepHeight + 50; ctx.fillStyle = '#7ef0d4'; @@ -3434,18 +3633,18 @@ function drawSpamClassification() { ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 18px sans-serif'; ctx.fillText('→ SPAM! 📧❌', padding, y4 + 30); - + // Create comparison chart at bottom if (!bayesComparisonChart) { const compCanvas = document.createElement('canvas'); compCanvas.id = 'bayes-comparison-chart'; compCanvas.style.marginTop = '20px'; canvas.parentElement.appendChild(compCanvas); - + if (bayesComparisonChart) { bayesComparisonChart.destroy(); } - + bayesComparisonChart = createVerifiedVisualization('bayes-comparison-chart', { type: 'bar', data: { @@ -3485,54 +3684,93 @@ function drawSpamClassification() { }, 'Naive Bayes', 'Spam Classification'); if (bayesComparisonChart) compCanvas.style.height = '150px'; } - + logViz('Naive Bayes', 'Bayes Theorem Flow', 'success'); } // Topic 16: Decision Trees function initDecisionTrees() { - const canvas1 = document.getElementById('decision-tree-viz'); - if (canvas1 && !canvas1.dataset.initialized) { - canvas1.dataset.initialized = 'true'; + ensureCanvasVisible('decision-tree-viz', drawDecisionTree); + ensureCanvasVisible('entropy-viz', drawEntropyViz); + ensureCanvasVisible('split-comparison', drawSplitComparison); + ensureCanvasVisible('tree-boundary', drawTreeBoundary); +} + +// Retry wrapper for decision tree drawings +function drawDecisionTreeWithRetry(retries = 3) { + const canvas = document.getElementById('decision-tree-viz'); + if (!canvas) return; + + // Check if canvas has proper dimensions + if (canvas.offsetWidth > 100) { + drawDecisionTree(); + } else if (retries > 0) { + // Retry after 500ms if dimensions aren't ready + setTimeout(() => drawDecisionTreeWithRetry(retries - 1), 500); + } else { + // Force draw with fallback dimensions drawDecisionTree(); } - - const canvas2 = document.getElementById('entropy-viz'); - if (canvas2 && !canvas2.dataset.initialized) { - canvas2.dataset.initialized = 'true'; +} + +function drawEntropyVizWithRetry(retries = 3) { + const canvas = document.getElementById('entropy-viz'); + if (!canvas) return; + + if (canvas.offsetWidth > 100) { + drawEntropyViz(); + } else if (retries > 0) { + setTimeout(() => drawEntropyVizWithRetry(retries - 1), 500); + } else { drawEntropyViz(); } - - const canvas3 = document.getElementById('split-comparison'); - if (canvas3 && !canvas3.dataset.initialized) { - canvas3.dataset.initialized = 'true'; +} + +function drawSplitComparisonWithRetry(retries = 3) { + const canvas = document.getElementById('split-comparison'); + if (!canvas) return; + + if (canvas.offsetWidth > 100) { + drawSplitComparison(); + } else if (retries > 0) { + setTimeout(() => drawSplitComparisonWithRetry(retries - 1), 500); + } else { drawSplitComparison(); } - - const canvas4 = document.getElementById('tree-boundary'); - if (canvas4 && !canvas4.dataset.initialized) { - canvas4.dataset.initialized = 'true'; +} + +function drawTreeBoundaryWithRetry(retries = 3) { + const canvas = document.getElementById('tree-boundary'); + if (!canvas) return; + + if (canvas.offsetWidth > 100) { + drawTreeBoundary(); + } else if (retries > 0) { + setTimeout(() => drawTreeBoundaryWithRetry(retries - 1), 500); + } else { drawTreeBoundary(); } } + function drawDecisionTree() { const canvas = document.getElementById('decision-tree-viz'); if (!canvas) { logViz('Decision Trees', 'Tree Structure', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 450; - + + // Clear and Fill Background ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const centerX = width / 2; - + // Node structure const nodes = [ { x: centerX, y: 60, text: 'Has "free"?', type: 'root' }, @@ -3543,7 +3781,7 @@ function drawDecisionTree() { { x: centerX + 80, y: 260, text: 'SPAM', type: 'leaf', class: 'spam' }, { x: centerX + 220, y: 260, text: 'NOT SPAM', type: 'leaf', class: 'not-spam' } ]; - + const edges = [ { from: 0, to: 1, label: 'Yes' }, { from: 0, to: 2, label: 'No' }, @@ -3552,7 +3790,7 @@ function drawDecisionTree() { { from: 2, to: 5, label: 'Yes' }, { from: 2, to: 6, label: 'No' } ]; - + // Draw edges ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 2; @@ -3563,7 +3801,7 @@ function drawDecisionTree() { ctx.moveTo(from.x, from.y + 25); ctx.lineTo(to.x, to.y - 25); ctx.stroke(); - + // Edge label ctx.fillStyle = '#7ef0d4'; ctx.font = '11px sans-serif'; @@ -3572,7 +3810,7 @@ function drawDecisionTree() { const midY = (from.y + to.y) / 2; ctx.fillText(edge.label, midX + 15, midY); }); - + // Draw nodes nodes.forEach(node => { if (node.type === 'leaf') { @@ -3582,90 +3820,90 @@ function drawDecisionTree() { ctx.fillStyle = '#6aa9ff33'; ctx.strokeStyle = '#6aa9ff'; } - + ctx.lineWidth = 2; ctx.beginPath(); ctx.rect(node.x - 60, node.y - 20, 120, 40); ctx.fill(); ctx.stroke(); - + ctx.fillStyle = '#e8eef6'; ctx.font = node.type === 'leaf' ? 'bold 13px sans-serif' : '12px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(node.text, node.x, node.y + 5); }); - + // Title ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; ctx.fillText('Decision Tree: Email Spam Classifier', centerX, 30); - + // Example path ctx.fillStyle = '#a9b4c2'; ctx.font = '12px sans-serif'; ctx.textAlign = 'left'; ctx.fillText('Example: Email with "free" + link → SPAM', 40, height - 20); - + logViz('Decision Trees', 'Tree Structure', 'success'); } function drawSplitComparison() { const canvas = document.getElementById('split-comparison'); if (!canvas) return; - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 400; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const splits = [ { name: 'Split A: "Contains FREE"', ig: 0.034, color: '#ff8c6a' }, { name: 'Split B: "Has Link"', ig: 0.156, color: '#7ef0d4' }, { name: 'Split C: "Urgent"', ig: 0.089, color: '#ffb490' } ]; - + const padding = 60; const barHeight = 60; const maxWidth = width - 2 * padding - 200; const maxIG = Math.max(...splits.map(s => s.ig)); - + splits.forEach((split, i) => { const y = 80 + i * (barHeight + 40); const barWidth = (split.ig / maxIG) * maxWidth; - + // Bar ctx.fillStyle = split.color; ctx.fillRect(padding, y, barWidth, barHeight); - + // Border ctx.strokeStyle = split.color; ctx.lineWidth = 2; ctx.strokeRect(padding, y, barWidth, barHeight); - + // Label ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 13px sans-serif'; ctx.textAlign = 'left'; ctx.fillText(split.name, padding, y - 10); - + // Value ctx.fillStyle = '#1a2332'; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(`IG = ${split.ig.toFixed(3)}`, padding + barWidth / 2, y + barHeight / 2 + 6); }); - + // Winner ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('✓ Best split: Highest Information Gain!', width / 2, height - 30); - + logViz('Decision Trees', 'Information Gain', 'success'); - + // Title ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; @@ -3678,19 +3916,19 @@ function drawEntropyViz() { logViz('Decision Trees', 'Entropy Visualization', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 400; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; - + // Draw entropy curve ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 3; @@ -3703,24 +3941,24 @@ function drawEntropyViz() { else ctx.lineTo(x, y); } ctx.stroke(); - + // Mark key points const points = [ { p: 0.1, label: 'Pure\n(low)' }, { p: 0.5, label: 'Maximum\n(high)' }, { p: 0.9, label: 'Pure\n(low)' } ]; - + points.forEach(point => { const entropy = -point.p * Math.log2(point.p) - (1 - point.p) * Math.log2(1 - point.p); const x = padding + point.p * chartWidth; const y = height - padding - entropy * chartHeight; - + ctx.fillStyle = '#7ef0d4'; ctx.beginPath(); ctx.arc(x, y, 6, 0, 2 * Math.PI); ctx.fill(); - + ctx.fillStyle = '#7ef0d4'; ctx.font = '11px sans-serif'; ctx.textAlign = 'center'; @@ -3729,7 +3967,7 @@ function drawEntropyViz() { ctx.fillText(line, x, y - 15 - (lines.length - 1 - i) * 12); }); }); - + // Axes ctx.strokeStyle = '#2a3544'; ctx.lineWidth = 2; @@ -3738,7 +3976,7 @@ function drawEntropyViz() { ctx.lineTo(padding, height - padding); ctx.lineTo(width - padding, height - padding); ctx.stroke(); - + // Labels ctx.fillStyle = '#a9b4c2'; ctx.font = '12px sans-serif'; @@ -3749,71 +3987,71 @@ function drawEntropyViz() { ctx.rotate(-Math.PI / 2); ctx.fillText('Entropy H(p)', 0, 0); ctx.restore(); - + // Title ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('Entropy: Measuring Disorder', width / 2, 30); - + logViz('Decision Trees', 'Entropy Visualization', 'success'); } function drawSplitComparison() { const canvas = document.getElementById('split-comparison'); if (!canvas) return; - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 400; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const splits = [ { name: 'Split A: "Contains FREE"', ig: 0.034, color: '#ff8c6a' }, { name: 'Split B: "Has Link"', ig: 0.156, color: '#7ef0d4' }, { name: 'Split C: "Urgent"', ig: 0.089, color: '#ffb490' } ]; - + const padding = 60; const barHeight = 60; const maxWidth = width - 2 * padding - 200; const maxIG = Math.max(...splits.map(s => s.ig)); - + splits.forEach((split, i) => { const y = 80 + i * (barHeight + 40); const barWidth = (split.ig / maxIG) * maxWidth; - + // Bar ctx.fillStyle = split.color; ctx.fillRect(padding, y, barWidth, barHeight); - + // Border ctx.strokeStyle = split.color; ctx.lineWidth = 2; ctx.strokeRect(padding, y, barWidth, barHeight); - + // Label ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 13px sans-serif'; ctx.textAlign = 'left'; ctx.fillText(split.name, padding, y - 10); - + // Value ctx.fillStyle = '#1a2332'; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(`IG = ${split.ig.toFixed(3)}`, padding + barWidth / 2, y + barHeight / 2 + 6); }); - + // Winner ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('✓ Best split: Highest Information Gain!', width / 2, height - 30); - + // Title ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; @@ -3823,19 +4061,19 @@ function drawSplitComparison() { function drawEntropyViz() { const canvas = document.getElementById('entropy-viz'); if (!canvas) return; - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 400; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; - + // Draw entropy curve ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 3; @@ -3848,24 +4086,24 @@ function drawEntropyViz() { else ctx.lineTo(x, y); } ctx.stroke(); - + // Mark key points const points = [ { p: 0.1, label: 'Pure\n(low)' }, { p: 0.5, label: 'Maximum\n(high)' }, { p: 0.9, label: 'Pure\n(low)' } ]; - + points.forEach(point => { const entropy = -point.p * Math.log2(point.p) - (1 - point.p) * Math.log2(1 - point.p); const x = padding + point.p * chartWidth; const y = height - padding - entropy * chartHeight; - + ctx.fillStyle = '#7ef0d4'; ctx.beginPath(); ctx.arc(x, y, 6, 0, 2 * Math.PI); ctx.fill(); - + ctx.fillStyle = '#7ef0d4'; ctx.font = '11px sans-serif'; ctx.textAlign = 'center'; @@ -3874,7 +4112,7 @@ function drawEntropyViz() { ctx.fillText(line, x, y - 15 - (lines.length - 1 - i) * 12); }); }); - + // Axes ctx.strokeStyle = '#2a3544'; ctx.lineWidth = 2; @@ -3883,7 +4121,7 @@ function drawEntropyViz() { ctx.lineTo(padding, height - padding); ctx.lineTo(width - padding, height - padding); ctx.stroke(); - + // Labels ctx.fillStyle = '#a9b4c2'; ctx.font = '12px sans-serif'; @@ -3894,7 +4132,7 @@ function drawEntropyViz() { ctx.rotate(-Math.PI / 2); ctx.fillText('Entropy H(p)', 0, 0); ctx.restore(); - + // Title ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; @@ -3908,19 +4146,19 @@ function drawTreeBoundary() { logViz('Decision Trees', 'Decision Regions', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 400; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; - + // Draw regions const regions = [ { x1: 0, y1: 0, x2: 0.5, y2: 0.6, class: 'orange' }, @@ -3928,21 +4166,21 @@ function drawTreeBoundary() { { x1: 0, y1: 0.6, x2: 0.3, y2: 1, class: 'yellow' }, { x1: 0.3, y1: 0.6, x2: 1, y2: 1, class: 'orange' } ]; - + regions.forEach(region => { const x = padding + region.x1 * chartWidth; const y = padding + region.y1 * chartHeight; const w = (region.x2 - region.x1) * chartWidth; const h = (region.y2 - region.y1) * chartHeight; - + ctx.fillStyle = region.class === 'orange' ? 'rgba(255, 140, 106, 0.2)' : 'rgba(255, 235, 59, 0.2)'; ctx.fillRect(x, y, w, h); - + ctx.strokeStyle = region.class === 'orange' ? '#ff8c6a' : '#ffeb3b'; ctx.lineWidth = 2; ctx.strokeRect(x, y, w, h); }); - + // Generate random points const orangePoints = []; const yellowPoints = []; @@ -3960,7 +4198,7 @@ function drawTreeBoundary() { yellowPoints.push({ x: Math.random() * 0.3, y: 0.6 + Math.random() * 0.4 }); } } - + // Draw points orangePoints.forEach(p => { ctx.fillStyle = '#ff8c6a'; @@ -3968,14 +4206,14 @@ function drawTreeBoundary() { ctx.arc(padding + p.x * chartWidth, padding + p.y * chartHeight, 5, 0, 2 * Math.PI); ctx.fill(); }); - + yellowPoints.forEach(p => { ctx.fillStyle = '#ffeb3b'; ctx.beginPath(); ctx.arc(padding + p.x * chartWidth, padding + p.y * chartHeight, 5, 0, 2 * Math.PI); ctx.fill(); }); - + // Labels ctx.fillStyle = '#a9b4c2'; ctx.font = '12px sans-serif'; @@ -3986,61 +4224,95 @@ function drawTreeBoundary() { ctx.rotate(-Math.PI / 2); ctx.fillText('Feature 2', 0, 0); ctx.restore(); - + // Title ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('Decision Tree Creates Rectangular Regions', width / 2, 30); - + logViz('Decision Trees', 'Decision Regions', 'success'); } -// Topic 16b: Decision Tree Regression Visualization function initDecisionTreeRegression() { const canvas1 = document.getElementById('dt-regression-canvas'); if (canvas1 && !canvas1.dataset.initialized) { + if (canvas1.offsetWidth === 0) { + setTimeout(initDecisionTreeRegression, 100); + return; + } canvas1.dataset.initialized = 'true'; drawDTRegression(); } - + const canvas2 = document.getElementById('dt-splits-canvas'); if (canvas2 && !canvas2.dataset.initialized) { + if (canvas2.offsetWidth === 0) { + setTimeout(initDecisionTreeRegression, 100); + return; + } canvas2.dataset.initialized = 'true'; drawDTSplits(); } } +function drawDTRegressionWithRetry(retries = 3) { + const canvas = document.getElementById('dt-regression-canvas'); + if (!canvas) return; + + if (canvas.offsetWidth > 100) { + drawDTRegression(); + } else if (retries > 0) { + setTimeout(() => drawDTRegressionWithRetry(retries - 1), 500); + } else { + drawDTRegression(); + } +} + +function drawDTSplitsWithRetry(retries = 3) { + const canvas = document.getElementById('dt-splits-canvas'); + if (!canvas) return; + + if (canvas.offsetWidth > 100) { + drawDTSplits(); + } else if (retries > 0) { + setTimeout(() => drawDTSplitsWithRetry(retries - 1), 500); + } else { + drawDTSplits(); + } +} + + function drawDTRegression() { const canvas = document.getElementById('dt-regression-canvas'); if (!canvas) { logViz('Decision Tree Regression', 'Splits & Predictions', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 450; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; - + const xMin = 700, xMax = 1800; const yMin = 40, yMax = 110; const scaleX = (x) => padding + ((x - xMin) / (xMax - xMin)) * chartWidth; const scaleY = (y) => height - padding - ((y - yMin) / (yMax - yMin)) * chartHeight; - + // Data points const data = [ - {x: 800, y: 50}, {x: 850, y: 52}, {x: 900, y: 54}, - {x: 1500, y: 90}, {x: 1600, y: 95}, {x: 1700, y: 100} + { x: 800, y: 50 }, { x: 850, y: 52 }, { x: 900, y: 54 }, + { x: 1500, y: 90 }, { x: 1600, y: 95 }, { x: 1700, y: 100 } ]; - + // Draw decision boundaries ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 3; @@ -4049,13 +4321,13 @@ function drawDTRegression() { ctx.moveTo(scaleX(1200), padding); ctx.lineTo(scaleX(1200), height - padding); ctx.stroke(); - + ctx.beginPath(); ctx.moveTo(scaleX(1550), padding); ctx.lineTo(scaleX(1550), height - padding); ctx.stroke(); ctx.setLineDash([]); - + // Draw regions with predictions ctx.fillStyle = 'rgba(126, 240, 212, 0.1)'; ctx.fillRect(scaleX(700), scaleY(52), scaleX(1200) - scaleX(700), height - padding - scaleY(52)); @@ -4063,7 +4335,7 @@ function drawDTRegression() { ctx.fillRect(scaleX(1200), scaleY(90), scaleX(1550) - scaleX(1200), height - padding - scaleY(90)); ctx.fillStyle = 'rgba(106, 169, 255, 0.1)'; ctx.fillRect(scaleX(1550), scaleY(97.5), scaleX(1800) - scaleX(1550), height - padding - scaleY(97.5)); - + // Draw prediction lines ctx.strokeStyle = '#7ef0d4'; ctx.lineWidth = 4; @@ -4071,19 +4343,19 @@ function drawDTRegression() { ctx.moveTo(scaleX(700), scaleY(52)); ctx.lineTo(scaleX(1200), scaleY(52)); ctx.stroke(); - + ctx.strokeStyle = '#ff8c6a'; ctx.beginPath(); ctx.moveTo(scaleX(1200), scaleY(90)); ctx.lineTo(scaleX(1550), scaleY(90)); ctx.stroke(); - + ctx.strokeStyle = '#6aa9ff'; ctx.beginPath(); ctx.moveTo(scaleX(1550), scaleY(97.5)); ctx.lineTo(scaleX(1800), scaleY(97.5)); ctx.stroke(); - + // Draw data points data.forEach(point => { ctx.fillStyle = '#e8eef6'; @@ -4094,7 +4366,7 @@ function drawDTRegression() { ctx.lineWidth = 2; ctx.stroke(); }); - + // Labels ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 13px sans-serif'; @@ -4104,7 +4376,7 @@ function drawDTRegression() { ctx.fillText('Predict: ₹90L', scaleX(1375), scaleY(90) - 10); ctx.fillStyle = '#6aa9ff'; ctx.fillText('Predict: ₹97.5L', scaleX(1650), scaleY(97.5) - 10); - + // Axes ctx.fillStyle = '#a9b4c2'; ctx.font = '12px sans-serif'; @@ -4115,12 +4387,12 @@ function drawDTRegression() { ctx.rotate(-Math.PI / 2); ctx.fillText('Price (Lakhs)', 0, 0); ctx.restore(); - + ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 14px sans-serif'; ctx.fillText('Split at 1200', scaleX(1200), 30); ctx.fillText('Split at 1550', scaleX(1550), 30); - + logViz('Decision Tree Regression', 'Splits & Predictions', 'success'); } @@ -4130,80 +4402,70 @@ function drawDTSplits() { logViz('Decision Tree Regression', 'Split Comparison', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 400; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const splits = [ - {value: 825, varReduction: 120, color: '#ff8c6a'}, - {value: 875, varReduction: 180, color: '#ffb490'}, - {value: 1200, varReduction: 462.25, color: '#7ef0d4'}, - {value: 1550, varReduction: 95, color: '#ffb490'}, - {value: 1650, varReduction: 65, color: '#ff8c6a'} + { value: 825, varReduction: 120, color: '#ff8c6a' }, + { value: 875, varReduction: 180, color: '#ffb490' }, + { value: 1200, varReduction: 462.25, color: '#7ef0d4' }, + { value: 1550, varReduction: 95, color: '#ffb490' }, + { value: 1650, varReduction: 65, color: '#ff8c6a' } ]; - + const padding = 60; const barHeight = 50; const maxWidth = width - 2 * padding - 200; const maxVR = Math.max(...splits.map(s => s.varReduction)); - + splits.forEach((split, i) => { const y = 60 + i * (barHeight + 25); const barWidth = (split.varReduction / maxVR) * maxWidth; - + ctx.fillStyle = split.color; ctx.fillRect(padding, y, barWidth, barHeight); ctx.strokeStyle = split.color; ctx.lineWidth = 2; ctx.strokeRect(padding, y, barWidth, barHeight); - + ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 12px sans-serif'; ctx.textAlign = 'left'; ctx.fillText(`Split at ${split.value}`, padding, y - 8); - + ctx.fillStyle = '#1a2332'; ctx.font = 'bold 14px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(`VR = ${split.varReduction.toFixed(1)}`, padding + barWidth / 2, y + barHeight / 2 + 5); }); - + ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('✓ Split at 1200: Maximum Variance Reduction!', width / 2, height - 20); - + logViz('Decision Tree Regression', 'Split Comparison', 'success'); } // Topic 17a: Gradient Boosting (NEW) function initGradientBoosting() { - const canvases = [ - { id: 'gb-sequential-canvas', fn: drawGBSequential }, - { id: 'gb-residuals-canvas', fn: drawGBResiduals }, - { id: 'gb-learning-rate-canvas', fn: drawGBLearningRate }, - { id: 'gb-stumps-canvas', fn: drawGBStumps }, - { id: 'gb-predictions-canvas', fn: drawGBPredictions } - ]; - - canvases.forEach(c => { - const canvas = document.getElementById(c.id); - if (canvas && !canvas.dataset.initialized) { - canvas.dataset.initialized = 'true'; - c.fn(); - } - }); + ensureCanvasVisible('gb-sequential-canvas', drawGBSequential); + ensureCanvasVisible('gb-residuals-canvas', drawGBResiduals); + ensureCanvasVisible('gb-learning-rate-canvas', drawGBLearningRate); + ensureCanvasVisible('gb-stumps-canvas', drawGBStumps); + ensureCanvasVisible('gb-predictions-canvas', drawGBPredictions); } function drawGBSequential() { const canvas = document.getElementById('gb-sequential-canvas'); if (!canvas) return; - + const gbData = [ { iteration: 0, f: 154, residual: 29.6 }, { iteration: 1, f: 151.93, residual: 26.8 }, @@ -4217,7 +4479,7 @@ function drawGBSequential() { { iteration: 9, f: 137.4, residual: 10.9 }, { iteration: 10, f: 136.3, residual: 9.8 } ]; - + createVerifiedVisualization('gb-sequential-canvas', { type: 'line', data: { @@ -4282,7 +4544,7 @@ function drawGBSequential() { function drawGBResiduals() { const canvas = document.getElementById('gb-residuals-canvas'); if (!canvas) return; - + const residuals = [ { id: 1, iter0: -34, iter1: -31.93, iter5: -12, iter10: -3 }, { id: 2, iter0: -24, iter1: -21.93, iter5: -8, iter10: -2 }, @@ -4290,7 +4552,7 @@ function drawGBResiduals() { { id: 4, iter0: 16, iter1: 12.90, iter5: 5, iter10: 1 }, { id: 5, iter0: 46, iter1: 42.90, iter5: 18, iter10: 4 } ]; - + createVerifiedVisualization('gb-residuals-canvas', { type: 'bar', data: { @@ -4348,9 +4610,9 @@ function drawGBResiduals() { function drawGBLearningRate() { const canvas = document.getElementById('gb-learning-rate-canvas'); if (!canvas) return; - - const iterations = Array.from({length: 21}, (_, i) => i); - + + const iterations = Array.from({ length: 21 }, (_, i) => i); + const lr01 = iterations.map(i => 154 - 18 * (1 - Math.exp(-i * 0.01))); const lr10 = iterations.map(i => 154 - 18 * (1 - Math.exp(-i * 0.1))); const lr100 = iterations.map(i => { @@ -4358,7 +4620,7 @@ function drawGBLearningRate() { if (i < 5) return 154 - 18 * (1 - Math.exp(-i * 1.0)); return 136 + Math.sin(i) * 2; }); - + createVerifiedVisualization('gb-learning-rate-canvas', { type: 'line', data: { @@ -4418,102 +4680,102 @@ function drawGBLearningRate() { function drawGBStumps() { const canvas = document.getElementById('gb-stumps-canvas'); if (!canvas) return; - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 400; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const stumps = [ { name: 'h1', split: 1050, left: -20.66, right: 31.0, color: '#6aa9ff' }, { name: 'h2', split: 950, left: -15.2, right: 22.5, color: '#7ef0d4' }, { name: 'h3', split: 1150, left: -8.5, right: 14.8, color: '#ffb490' } ]; - + const stumpWidth = width / 3; - + stumps.forEach((stump, idx) => { const offsetX = idx * stumpWidth; const centerX = offsetX + stumpWidth / 2; - + // Title ctx.fillStyle = stump.color; ctx.font = 'bold 14px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(stump.name, centerX, 30); - + // Root node ctx.fillStyle = stump.color + '33'; ctx.fillRect(centerX - 40, 60, 80, 50); ctx.strokeStyle = stump.color; ctx.lineWidth = 2; ctx.strokeRect(centerX - 40, 60, 80, 50); - + ctx.fillStyle = '#e8eef6'; ctx.font = '12px sans-serif'; ctx.fillText('Size <', centerX, 80); ctx.fillText(stump.split, centerX, 95); - + // Left child ctx.strokeStyle = stump.color; ctx.beginPath(); ctx.moveTo(centerX, 110); ctx.lineTo(centerX - 50, 180); ctx.stroke(); - + ctx.fillStyle = '#7ef0d4' + '33'; ctx.fillRect(centerX - 85, 180, 70, 50); ctx.strokeStyle = '#7ef0d4'; ctx.strokeRect(centerX - 85, 180, 70, 50); - + ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 13px sans-serif'; ctx.fillText(stump.left.toFixed(2), centerX - 50, 210); - + // Right child ctx.strokeStyle = stump.color; ctx.beginPath(); ctx.moveTo(centerX, 110); ctx.lineTo(centerX + 50, 180); ctx.stroke(); - + ctx.fillStyle = '#ff8c6a' + '33'; ctx.fillRect(centerX + 15, 180, 70, 50); ctx.strokeStyle = '#ff8c6a'; ctx.strokeRect(centerX + 15, 180, 70, 50); - + ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 13px sans-serif'; ctx.fillText(stump.right.toFixed(2), centerX + 50, 210); - + // Labels ctx.fillStyle = '#a9b4c2'; ctx.font = '10px sans-serif'; ctx.fillText('≤', centerX - 50, 150); ctx.fillText('>', centerX + 50, 150); }); - + // Title ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('Weak Learner Stumps (Depth = 1)', width / 2, height - 20); - + logViz('Gradient Boosting', 'Weak Learner Stumps', 'success'); } function drawGBPredictions() { const canvas = document.getElementById('gb-predictions-canvas'); if (!canvas) return; - + const actual = [120, 130, 150, 170, 200]; const iter0 = [154, 154, 154, 154, 154]; const iter5 = [125, 135, 148, 165, 195]; const iter10 = [121, 131, 149, 169, 199]; - + createVerifiedVisualization('gb-predictions-canvas', { type: 'scatter', data: { @@ -4574,34 +4836,24 @@ function drawGBPredictions() { // Topic 17b: XGBoost (NEW) function initXGBoost() { - const canvases = [ - { id: 'xgb-gain-canvas', fn: drawXGBGain }, - { id: 'xgb-regularization-canvas', fn: drawXGBRegularization }, - { id: 'xgb-hessian-canvas', fn: drawXGBHessian }, - { id: 'xgb-leaf-weights-canvas', fn: drawXGBLeafWeights }, - { id: 'xgb-comparison-canvas', fn: drawXGBComparison } - ]; - - canvases.forEach(c => { - const canvas = document.getElementById(c.id); - if (canvas && !canvas.dataset.initialized) { - canvas.dataset.initialized = 'true'; - c.fn(); - } - }); + ensureCanvasVisible('xgb-gain-canvas', drawXGBGain); + ensureCanvasVisible('xgb-regularization-canvas', drawXGBRegularization); + ensureCanvasVisible('xgb-hessian-canvas', drawXGBHessian); + ensureCanvasVisible('xgb-leaf-weights-canvas', drawXGBLeafWeights); + ensureCanvasVisible('xgb-comparison-canvas', drawXGBComparison); } function drawXGBGain() { const canvas = document.getElementById('xgb-gain-canvas'); if (!canvas) return; - + const splits = [ { threshold: 850, gl: -58, gr: 0, hl: 2, hr: 3, gain: 1121 }, { threshold: 950, gl: -58, gr: 58, hl: 2, hr: 3, gain: 1962 }, { threshold: 1050, gl: -62, gr: 62, hl: 3, hr: 2, gain: 1842 }, { threshold: 1150, gl: -4, gr: 4, hl: 4, hr: 1, gain: 892 } ]; - + createVerifiedVisualization('xgb-gain-canvas', { type: 'bar', data: { @@ -4664,11 +4916,11 @@ function drawXGBGain() { function drawXGBRegularization() { const canvas = document.getElementById('xgb-regularization-canvas'); if (!canvas) return; - + const lambdas = ['λ=0', 'λ=1', 'λ=10']; const trainAcc = [0.99, 0.95, 0.88]; const testAcc = [0.82, 0.93, 0.91]; - + createVerifiedVisualization('xgb-regularization-canvas', { type: 'bar', data: { @@ -4718,25 +4970,25 @@ function drawXGBRegularization() { function drawXGBHessian() { const canvas = document.getElementById('xgb-hessian-canvas'); if (!canvas) return; - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 400; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; - + // Draw surface comparison ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('Hessian Provides Curvature Information', width / 2, 30); - + // Draw gradient only curve ctx.strokeStyle = '#ff8c6a'; ctx.lineWidth = 3; @@ -4747,7 +4999,7 @@ function drawXGBHessian() { else ctx.lineTo(padding + x * chartWidth / 10, y); } ctx.stroke(); - + // Draw gradient + hessian curve ctx.strokeStyle = '#7ef0d4'; ctx.lineWidth = 3; @@ -4758,13 +5010,13 @@ function drawXGBHessian() { else ctx.lineTo(padding + x * chartWidth / 10, y); } ctx.stroke(); - + // Optimum point ctx.fillStyle = '#7ef0d4'; ctx.beginPath(); ctx.arc(padding + 5 * chartWidth / 10, 80, 8, 0, 2 * Math.PI); ctx.fill(); - + // Legend ctx.fillStyle = '#ff8c6a'; ctx.fillRect(padding + 10, height - 80, 20, 3); @@ -4772,31 +5024,31 @@ function drawXGBHessian() { ctx.font = '12px sans-serif'; ctx.textAlign = 'left'; ctx.fillText('1st order only (slower)', padding + 40, height - 75); - + ctx.fillStyle = '#7ef0d4'; ctx.fillRect(padding + 10, height - 55, 20, 3); ctx.fillStyle = '#e8eef6'; ctx.fillText('1st + 2nd order (faster)', padding + 40, height - 50); - + logViz('XGBoost', 'Hessian Contribution', 'success'); } function drawXGBLeafWeights() { const canvas = document.getElementById('xgb-leaf-weights-canvas'); if (!canvas) return; - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 350; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 40; const boxWidth = 300; const boxHeight = 120; - + // Left leaf const leftX = width / 4 - boxWidth / 2; ctx.fillStyle = '#7ef0d4' + '22'; @@ -4804,7 +5056,7 @@ function drawXGBLeafWeights() { ctx.strokeStyle = '#7ef0d4'; ctx.lineWidth = 3; ctx.strokeRect(leftX, 80, boxWidth, boxHeight); - + ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 14px sans-serif'; ctx.textAlign = 'left'; @@ -4816,7 +5068,7 @@ function drawXGBLeafWeights() { ctx.font = 'bold 16px monospace'; ctx.fillStyle = '#7ef0d4'; ctx.fillText(' = 19.33', leftX + 10, 190); - + // Right leaf const rightX = 3 * width / 4 - boxWidth / 2; ctx.fillStyle = '#ff8c6a' + '22'; @@ -4824,7 +5076,7 @@ function drawXGBLeafWeights() { ctx.strokeStyle = '#ff8c6a'; ctx.lineWidth = 3; ctx.strokeRect(rightX, 80, boxWidth, boxHeight); - + ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 14px sans-serif'; ctx.textAlign = 'left'; @@ -4836,25 +5088,25 @@ function drawXGBLeafWeights() { ctx.font = 'bold 16px monospace'; ctx.fillStyle = '#ff8c6a'; ctx.fillText(' = -14.5', rightX + 10, 190); - + // Title ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('Leaf Weight Calculation (λ = 1)', width / 2, 40); - + // Formula reminder ctx.fillStyle = '#a9b4c2'; ctx.font = '13px sans-serif'; ctx.fillText('Negative gradient divided by (Hessian + regularization)', width / 2, height - 20); - + logViz('XGBoost', 'Leaf Weight Calculation', 'success'); } function drawXGBComparison() { const canvas = document.getElementById('xgb-comparison-canvas'); if (!canvas) return; - + createVerifiedVisualization('xgb-comparison-canvas', { type: 'radar', data: { @@ -4907,6 +5159,10 @@ function drawXGBComparison() { function initBagging() { const canvas = document.getElementById('bagging-complete-canvas'); if (canvas && !canvas.dataset.initialized) { + if (canvas.offsetWidth === 0) { + setTimeout(initBagging, 100); + return; + } canvas.dataset.initialized = 'true'; drawBaggingCompleteViz(); } @@ -4914,7 +5170,14 @@ function initBagging() { function initBoostingAdaBoost() { const canvas = document.getElementById('boosting-complete-canvas'); - if (canvas && !canvas.dataset.initialized) { + if (canvas) { + if (canvas.dataset.initialized === 'true' && canvas.offsetWidth > 100) return; + + if (canvas.offsetWidth < 100) { + setTimeout(initBoostingAdaBoost, 200); + return; + } + canvas.dataset.initialized = 'true'; drawBoostingCompleteViz(); } @@ -4922,7 +5185,14 @@ function initBoostingAdaBoost() { function initRandomForest() { const canvas = document.getElementById('rf-complete-canvas'); - if (canvas && !canvas.dataset.initialized) { + if (canvas) { + if (canvas.dataset.initialized === 'true' && canvas.offsetWidth > 100) return; + + if (canvas.offsetWidth < 100) { + setTimeout(initRandomForest, 200); + return; + } + canvas.dataset.initialized = 'true'; drawRandomForestCompleteViz(); } @@ -4930,41 +5200,12 @@ function initRandomForest() { // Topic 17: Ensemble Methods function initEnsembleMethods() { - const canvasNew1 = document.getElementById('bagging-complete-canvas'); - if (canvasNew1 && !canvasNew1.dataset.initialized) { - canvasNew1.dataset.initialized = 'true'; - drawBaggingCompleteViz(); - } - - const canvasNew2 = document.getElementById('boosting-complete-canvas'); - if (canvasNew2 && !canvasNew2.dataset.initialized) { - canvasNew2.dataset.initialized = 'true'; - drawBoostingCompleteViz(); - } - - const canvasNew3 = document.getElementById('rf-complete-canvas'); - if (canvasNew3 && !canvasNew3.dataset.initialized) { - canvasNew3.dataset.initialized = 'true'; - drawRandomForestCompleteViz(); - } - - const canvas1 = document.getElementById('bagging-viz'); - if (canvas1 && !canvas1.dataset.initialized) { - canvas1.dataset.initialized = 'true'; - drawBaggingViz(); - } - - const canvas2 = document.getElementById('boosting-viz'); - if (canvas2 && !canvas2.dataset.initialized) { - canvas2.dataset.initialized = 'true'; - drawBoostingViz(); - } - - const canvas3 = document.getElementById('random-forest-viz'); - if (canvas3 && !canvas3.dataset.initialized) { - canvas3.dataset.initialized = 'true'; - drawRandomForestViz(); - } + ensureCanvasVisible('bagging-ensemble-canvas', drawBaggingEnsembleViz); + ensureCanvasVisible('boosting-ensemble-canvas', drawBoostingEnsembleViz); + ensureCanvasVisible('rf-ensemble-canvas', drawRandomForestEnsembleViz); + ensureCanvasVisible('bagging-viz', drawBaggingViz); + ensureCanvasVisible('boosting-viz', drawBoostingViz); + ensureCanvasVisible('random-forest-viz', drawRandomForestViz); } function drawBaggingCompleteViz() { @@ -4973,46 +5214,46 @@ function drawBaggingCompleteViz() { logViz('Ensemble Methods', 'Bagging Complete', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 400; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const treeY = 100; const predY = 280; const finalY = 350; - + // Three trees for (let i = 0; i < 3; i++) { const x = 150 + i * 250; const preds = [75, 72, 78]; - + // Tree box ctx.fillStyle = '#7ef0d433'; ctx.fillRect(x - 50, treeY, 100, 60); ctx.strokeStyle = '#7ef0d4'; ctx.lineWidth = 2; ctx.strokeRect(x - 50, treeY, 100, 60); - + ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 14px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(`Tree ${i + 1}`, x, treeY + 35); - + // Prediction ctx.fillStyle = '#6aa9ff33'; ctx.fillRect(x - 40, predY, 80, 50); ctx.strokeStyle = '#6aa9ff'; ctx.strokeRect(x - 40, predY, 80, 50); - + ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 16px sans-serif'; ctx.fillText(`₹${preds[i]}L`, x, predY + 32); - + // Arrow to final ctx.strokeStyle = '#7ef0d4'; ctx.lineWidth = 2; @@ -5021,24 +5262,24 @@ function drawBaggingCompleteViz() { ctx.lineTo(width / 2, finalY - 10); ctx.stroke(); } - + // Final average ctx.fillStyle = '#ff8c6a33'; ctx.fillRect(width / 2 - 80, finalY, 160, 50); ctx.strokeStyle = '#ff8c6a'; ctx.lineWidth = 3; ctx.strokeRect(width / 2 - 80, finalY, 160, 50); - + ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 18px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('Avg = ₹75L ✓', width / 2, finalY + 32); - + // Title ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; ctx.fillText('Bagging: Average of 3 Trees', width / 2, 30); - + logViz('Ensemble Methods', 'Bagging Complete', 'success'); } @@ -5048,41 +5289,41 @@ function drawBoostingCompleteViz() { logViz('Ensemble Methods', 'Boosting Complete', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 450; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const rounds = [ - {label: 'Round 1', weights: [1, 1, 1, 1, 1, 1], errors: [20, 20, 21, 2, 3, 2]}, - {label: 'Round 2', weights: [1, 1, 1, 2.5, 3, 2.5], errors: [21, 21, 20, 0, 1, 0]}, - {label: 'Round 3', weights: [2, 2, 2, 1, 1, 1], errors: [20, 20, 21, 1, 2, 1]} + { label: 'Round 1', weights: [1, 1, 1, 1, 1, 1], errors: [20, 20, 21, 2, 3, 2] }, + { label: 'Round 2', weights: [1, 1, 1, 2.5, 3, 2.5], errors: [21, 21, 20, 0, 1, 0] }, + { label: 'Round 3', weights: [2, 2, 2, 1, 1, 1], errors: [20, 20, 21, 1, 2, 1] } ]; - + const startX = 60; const barWidth = 30; const gap = 10; - + rounds.forEach((round, r) => { const y = 80 + r * 120; - + ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 14px sans-serif'; ctx.textAlign = 'left'; ctx.fillText(round.label, 10, y + 20); - + // Weight bars round.weights.forEach((w, i) => { const x = startX + i * (barWidth + gap); const h = w * 20; - + ctx.fillStyle = w > 1.5 ? '#ff8c6a' : '#6aa9ff'; ctx.fillRect(x, y + 40 - h, barWidth, h); - + // Error text ctx.fillStyle = '#a9b4c2'; ctx.font = '9px sans-serif'; @@ -5090,7 +5331,7 @@ function drawBoostingCompleteViz() { ctx.fillText(`e=${round.errors[i]}`, x + barWidth / 2, y + 55); }); }); - + ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center'; @@ -5104,26 +5345,275 @@ function drawRandomForestCompleteViz() { logViz('Ensemble Methods', 'Random Forest Complete', 'failed', 'Canvas not found'); return; } - + + const ctx = canvas.getContext('2d'); + const width = canvas.width = canvas.offsetWidth || 600; + const height = canvas.height = 500; + + ctx.clearRect(0, 0, width, height); + ctx.fillStyle = '#1a2332'; + ctx.fillRect(0, 0, width, height); + + // Show 3 trees with feature randomness + const trees = [ + { features: ['Sq Ft', 'Age'], pred: 74 }, + { features: ['Sq Ft', 'Beds'], pred: 76 }, + { features: ['Beds', 'Age'], pred: 75 } + ]; + + trees.forEach((tree, i) => { + const x = 120 + i * 260; + const y = 100; + + // Bootstrap + ctx.fillStyle = '#6aa9ff33'; + ctx.fillRect(x - 60, y, 120, 50); + ctx.strokeStyle = '#6aa9ff'; + ctx.lineWidth = 2; + ctx.strokeRect(x - 60, y, 120, 50); + ctx.fillStyle = '#e8eef6'; + ctx.font = '12px sans-serif'; + ctx.textAlign = 'center'; + ctx.fillText('Bootstrap', x, y + 25); + ctx.fillText(`Sample ${i + 1}`, x, y + 40); + + // Tree with random features + ctx.fillStyle = '#7ef0d433'; + ctx.fillRect(x - 60, y + 80, 120, 70); + ctx.strokeStyle = '#7ef0d4'; + ctx.strokeRect(x - 60, y + 80, 120, 70); + ctx.fillStyle = '#e8eef6'; + ctx.font = 'bold 13px sans-serif'; + ctx.fillText(`Tree ${i + 1}`, x, y + 105); + ctx.font = '10px sans-serif'; + ctx.fillStyle = '#ffb490'; + ctx.fillText('Random:', x, y + 123); + ctx.fillText(tree.features.join(', '), x, y + 138); + + // Prediction + ctx.fillStyle = '#ff8c6a33'; + ctx.fillRect(x - 50, y + 180, 100, 50); + ctx.strokeStyle = '#ff8c6a'; + ctx.strokeRect(x - 50, y + 180, 100, 50); + ctx.fillStyle = '#e8eef6'; + ctx.font = 'bold 16px sans-serif'; + ctx.fillText(`₹${tree.pred}L`, x, y + 210); + + // Arrow to final + ctx.strokeStyle = '#7ef0d4'; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(x, y + 230); + ctx.lineTo(width / 2, y + 300); + ctx.stroke(); + }); + + // Final average + ctx.fillStyle = '#7ef0d433'; + ctx.fillRect(width / 2 - 100, 400, 200, 70); + ctx.strokeStyle = '#7ef0d4'; + ctx.lineWidth = 3; + ctx.strokeRect(width / 2 - 100, 400, 200, 70); + ctx.fillStyle = '#e8eef6'; + ctx.font = 'bold 18px sans-serif'; + ctx.textAlign = 'center'; + ctx.fillText('Average of 100 Trees', width / 2, 425); + ctx.fillText('= ₹75.2L ± ₹2.3L ✓', width / 2, 450); + + // Title + ctx.fillStyle = '#7ef0d4'; + ctx.font = 'bold 16px sans-serif'; + ctx.fillText('Random Forest: Bootstrap + Feature Randomness', width / 2, 30); + + logViz('Ensemble Methods', 'Random Forest Complete', 'success'); +} + +// Ensemble section specific drawing functions +function drawBaggingEnsembleViz() { + const canvas = document.getElementById('bagging-ensemble-canvas'); + if (!canvas) { + logViz('Ensemble Methods', 'Bagging Ensemble', 'failed', 'Canvas not found'); + return; + } + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; + const height = canvas.height = 400; + + ctx.clearRect(0, 0, width, height); + ctx.fillStyle = '#1a2332'; + ctx.fillRect(0, 0, width, height); + + const treeY = 100; + const predY = 280; + const finalY = 350; + + // Three trees + for (let i = 0; i < 3; i++) { + const x = 150 + i * 250; + const preds = [75, 72, 78]; + + // Tree box + ctx.fillStyle = '#7ef0d433'; + ctx.fillRect(x - 50, treeY, 100, 60); + ctx.strokeStyle = '#7ef0d4'; + ctx.lineWidth = 2; + ctx.strokeRect(x - 50, treeY, 100, 60); + + ctx.fillStyle = '#e8eef6'; + ctx.font = 'bold 14px sans-serif'; + ctx.textAlign = 'center'; + ctx.fillText(`Tree ${i + 1}`, x, treeY + 35); + + // Prediction + ctx.fillStyle = '#6aa9ff33'; + ctx.fillRect(x - 40, predY, 80, 50); + ctx.strokeStyle = '#6aa9ff'; + ctx.strokeRect(x - 40, predY, 80, 50); + + ctx.fillStyle = '#e8eef6'; + ctx.font = 'bold 16px sans-serif'; + ctx.fillText(`₹${preds[i]}L`, x, predY + 32); + + // Arrow to final + ctx.strokeStyle = '#7ef0d4'; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(x, predY + 50); + ctx.lineTo(width / 2, finalY - 10); + ctx.stroke(); + } + + // Final average + ctx.fillStyle = '#ff8c6a33'; + ctx.fillRect(width / 2 - 80, finalY, 160, 50); + ctx.strokeStyle = '#ff8c6a'; + ctx.lineWidth = 3; + ctx.strokeRect(width / 2 - 80, finalY, 160, 50); + + ctx.fillStyle = '#e8eef6'; + ctx.font = 'bold 18px sans-serif'; + ctx.textAlign = 'center'; + ctx.fillText('Avg = ₹75L ✓', width / 2, finalY + 32); + + // Title + ctx.fillStyle = '#7ef0d4'; + ctx.font = 'bold 16px sans-serif'; + ctx.fillText('Bagging: Average of 3 Trees', width / 2, 30); + + logViz('Ensemble Methods', 'Bagging Ensemble', 'success'); +} + +function drawBoostingEnsembleViz() { + const canvas = document.getElementById('boosting-ensemble-canvas'); + if (!canvas) { + logViz('Ensemble Methods', 'Boosting Ensemble', 'failed', 'Canvas not found'); + return; + } + + const ctx = canvas.getContext('2d'); + const width = canvas.width = canvas.offsetWidth || 600; + const height = canvas.height = 450; + + ctx.clearRect(0, 0, width, height); + ctx.fillStyle = '#1a2332'; + ctx.fillRect(0, 0, width, height); + + // Show boosting rounds + const rounds = [ + { stump: 'Sq Ft > 1200', pred: 50, error: '40%', weight: '0.34' }, + { stump: 'Age > 10', pred: 20, error: '25%', weight: '0.55' }, + { stump: 'Beds > 3', pred: 5, error: '15%', weight: '0.87' } + ]; + + rounds.forEach((round, i) => { + const x = 120 + i * 260; + const y = 80; + + // Round header + ctx.fillStyle = '#6aa9ff'; + ctx.font = 'bold 14px sans-serif'; + ctx.textAlign = 'center'; + ctx.fillText(`Round ${i + 1}`, x, y); + + // Stump box + ctx.fillStyle = '#7ef0d433'; + ctx.fillRect(x - 70, y + 20, 140, 60); + ctx.strokeStyle = '#7ef0d4'; + ctx.lineWidth = 2; + ctx.strokeRect(x - 70, y + 20, 140, 60); + ctx.fillStyle = '#e8eef6'; + ctx.font = '12px sans-serif'; + ctx.fillText(round.stump, x, y + 45); + ctx.font = '11px sans-serif'; + ctx.fillStyle = '#ff8c6a'; + ctx.fillText(`Error: ${round.error}`, x, y + 65); + + // Weight + ctx.fillStyle = '#ff8c6a33'; + ctx.fillRect(x - 50, y + 100, 100, 50); + ctx.strokeStyle = '#ff8c6a'; + ctx.strokeRect(x - 50, y + 100, 100, 50); + ctx.fillStyle = '#e8eef6'; + ctx.font = 'bold 14px sans-serif'; + ctx.fillText(`α = ${round.weight}`, x, y + 130); + + // Prediction + ctx.fillStyle = '#6aa9ff33'; + ctx.fillRect(x - 40, y + 170, 80, 50); + ctx.strokeStyle = '#6aa9ff'; + ctx.strokeRect(x - 40, y + 170, 80, 50); + ctx.fillStyle = '#e8eef6'; + ctx.font = 'bold 14px sans-serif'; + ctx.fillText(`+₹${round.pred}L`, x, y + 200); + }); + + // Final weighted sum + ctx.fillStyle = '#7ef0d433'; + ctx.fillRect(width / 2 - 120, 350, 240, 70); + ctx.strokeStyle = '#7ef0d4'; + ctx.lineWidth = 3; + ctx.strokeRect(width / 2 - 120, 350, 240, 70); + ctx.fillStyle = '#e8eef6'; + ctx.font = 'bold 16px sans-serif'; + ctx.textAlign = 'center'; + ctx.fillText('Weighted Sum:', width / 2, 375); + ctx.fillText('0.34×50 + 0.55×20 + 0.87×5 = ₹75L ✓', width / 2, 400); + + // Title + ctx.fillStyle = '#ff8c6a'; + ctx.font = 'bold 16px sans-serif'; + ctx.fillText('Boosting: Sequential Learning', width / 2, 30); + + logViz('Ensemble Methods', 'Boosting Ensemble', 'success'); +} + +function drawRandomForestEnsembleViz() { + const canvas = document.getElementById('rf-ensemble-canvas'); + if (!canvas) { + logViz('Ensemble Methods', 'RF Ensemble', 'failed', 'Canvas not found'); + return; + } + + const ctx = canvas.getContext('2d'); + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 500; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + // Show 3 trees with feature randomness const trees = [ - {features: ['Sq Ft', 'Age'], pred: 74}, - {features: ['Sq Ft', 'Beds'], pred: 76}, - {features: ['Beds', 'Age'], pred: 75} + { features: ['Sq Ft', 'Age'], pred: 74 }, + { features: ['Sq Ft', 'Beds'], pred: 76 }, + { features: ['Beds', 'Age'], pred: 75 } ]; - + trees.forEach((tree, i) => { const x = 120 + i * 260; const y = 100; - + // Bootstrap ctx.fillStyle = '#6aa9ff33'; ctx.fillRect(x - 60, y, 120, 50); @@ -5135,7 +5625,7 @@ function drawRandomForestCompleteViz() { ctx.textAlign = 'center'; ctx.fillText('Bootstrap', x, y + 25); ctx.fillText(`Sample ${i + 1}`, x, y + 40); - + // Tree with random features ctx.fillStyle = '#7ef0d433'; ctx.fillRect(x - 60, y + 80, 120, 70); @@ -5148,7 +5638,7 @@ function drawRandomForestCompleteViz() { ctx.fillStyle = '#ffb490'; ctx.fillText('Random:', x, y + 123); ctx.fillText(tree.features.join(', '), x, y + 138); - + // Prediction ctx.fillStyle = '#ff8c6a33'; ctx.fillRect(x - 50, y + 180, 100, 50); @@ -5157,7 +5647,7 @@ function drawRandomForestCompleteViz() { ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 16px sans-serif'; ctx.fillText(`₹${tree.pred}L`, x, y + 210); - + // Arrow to final ctx.strokeStyle = '#7ef0d4'; ctx.lineWidth = 2; @@ -5166,7 +5656,7 @@ function drawRandomForestCompleteViz() { ctx.lineTo(width / 2, y + 300); ctx.stroke(); }); - + // Final average ctx.fillStyle = '#7ef0d433'; ctx.fillRect(width / 2 - 100, 400, 200, 70); @@ -5178,13 +5668,13 @@ function drawRandomForestCompleteViz() { ctx.textAlign = 'center'; ctx.fillText('Average of 100 Trees', width / 2, 425); ctx.fillText('= ₹75.2L ± ₹2.3L ✓', width / 2, 450); - + // Title ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; ctx.fillText('Random Forest: Bootstrap + Feature Randomness', width / 2, 30); - - logViz('Ensemble Methods', 'Random Forest Complete', 'success'); + + logViz('Ensemble Methods', 'RF Ensemble', 'success'); } function drawBaggingViz() { @@ -5193,20 +5683,20 @@ function drawBaggingViz() { logViz('Ensemble Methods', 'Bagging Viz', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 400; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const boxWidth = 150; const boxHeight = 60; const startY = 60; const spacing = (width - 3 * boxWidth) / 4; - + // Original data ctx.fillStyle = '#6aa9ff33'; ctx.fillRect(width / 2 - 100, startY, 200, boxHeight); @@ -5217,12 +5707,12 @@ function drawBaggingViz() { ctx.font = 'bold 14px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('Original Dataset', width / 2, startY + boxHeight / 2 + 5); - + // Bootstrap samples const sampleY = startY + boxHeight + 60; for (let i = 0; i < 3; i++) { const x = spacing + i * (boxWidth + spacing); - + // Arrow ctx.strokeStyle = '#7ef0d4'; ctx.lineWidth = 2; @@ -5230,31 +5720,31 @@ function drawBaggingViz() { ctx.moveTo(width / 2, startY + boxHeight); ctx.lineTo(x + boxWidth / 2, sampleY); ctx.stroke(); - + // Sample box ctx.fillStyle = '#7ef0d433'; ctx.fillRect(x, sampleY, boxWidth, boxHeight); ctx.strokeStyle = '#7ef0d4'; ctx.strokeRect(x, sampleY, boxWidth, boxHeight); - + ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 12px sans-serif'; ctx.fillText(`Bootstrap ${i + 1}`, x + boxWidth / 2, sampleY + boxHeight / 2 - 5); ctx.font = '10px sans-serif'; ctx.fillStyle = '#a9b4c2'; ctx.fillText('(random sample)', x + boxWidth / 2, sampleY + boxHeight / 2 + 10); - + // Model const modelY = sampleY + boxHeight + 40; ctx.fillStyle = '#ffb49033'; ctx.fillRect(x, modelY, boxWidth, boxHeight); ctx.strokeStyle = '#ffb490'; ctx.strokeRect(x, modelY, boxWidth, boxHeight); - + ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 12px sans-serif'; ctx.fillText(`Model ${i + 1}`, x + boxWidth / 2, modelY + boxHeight / 2 + 5); - + // Arrow to final ctx.strokeStyle = '#ffb490'; ctx.beginPath(); @@ -5262,7 +5752,7 @@ function drawBaggingViz() { ctx.lineTo(width / 2, height - 60); ctx.stroke(); } - + // Final prediction ctx.fillStyle = '#ff8c6a33'; ctx.fillRect(width / 2 - 100, height - 60, 200, boxHeight); @@ -5272,7 +5762,7 @@ function drawBaggingViz() { ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 14px sans-serif'; ctx.fillText('Average / Vote', width / 2, height - 60 + boxHeight / 2 + 5); - + // Title ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; @@ -5284,30 +5774,30 @@ function drawBaggingViz() { function drawBoostingViz() { const canvas = document.getElementById('boosting-viz'); if (!canvas) return; - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 450; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const iterY = [80, 180, 280]; const dataX = 100; const modelX = width / 2; const predX = width - 150; - + for (let i = 0; i < 3; i++) { const y = iterY[i]; const alpha = i === 0 ? 1 : (i === 1 ? 0.7 : 0.5); - + // Iteration label ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 14px sans-serif'; ctx.textAlign = 'left'; ctx.fillText(`Iteration ${i + 1}`, 20, y + 30); - + // Data with weights ctx.globalAlpha = alpha; ctx.fillStyle = '#6aa9ff33'; @@ -5316,7 +5806,7 @@ function drawBoostingViz() { ctx.lineWidth = 2; ctx.strokeRect(dataX, y, 120, 60); ctx.globalAlpha = 1; - + ctx.fillStyle = '#e8eef6'; ctx.font = '12px sans-serif'; ctx.textAlign = 'center'; @@ -5324,7 +5814,7 @@ function drawBoostingViz() { ctx.fillStyle = i > 0 ? '#ff8c6a' : '#7ef0d4'; ctx.font = 'bold 11px sans-serif'; ctx.fillText(i > 0 ? '↑ Focus on errors' : 'Equal weights', dataX + 60, y + 45); - + // Arrow ctx.strokeStyle = '#7ef0d4'; ctx.lineWidth = 2; @@ -5332,37 +5822,37 @@ function drawBoostingViz() { ctx.moveTo(dataX + 120, y + 30); ctx.lineTo(modelX - 60, y + 30); ctx.stroke(); - + // Model ctx.fillStyle = '#ffb49033'; ctx.fillRect(modelX - 60, y, 120, 60); ctx.strokeStyle = '#ffb490'; ctx.strokeRect(modelX - 60, y, 120, 60); - + ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 12px sans-serif'; ctx.fillText(`Model ${i + 1}`, modelX, y + 35); - + // Arrow ctx.strokeStyle = '#ffb490'; ctx.beginPath(); ctx.moveTo(modelX + 60, y + 30); ctx.lineTo(predX - 60, y + 30); ctx.stroke(); - + // Predictions ctx.fillStyle = '#7ef0d433'; ctx.fillRect(predX - 60, y, 120, 60); ctx.strokeStyle = '#7ef0d4'; ctx.strokeRect(predX - 60, y, 120, 60); - + ctx.fillStyle = '#e8eef6'; ctx.font = '11px sans-serif'; ctx.fillText('Predictions', predX, y + 25); ctx.fillStyle = i < 2 ? '#ff8c6a' : '#7ef0d4'; ctx.font = 'bold 10px sans-serif'; ctx.fillText(i < 2 ? 'Some errors' : 'Better!', predX, y + 45); - + // Feedback arrow if (i < 2) { ctx.strokeStyle = '#ff8c6a'; @@ -5373,77 +5863,48 @@ function drawBoostingViz() { ctx.lineTo(dataX + 60, y + 90); ctx.stroke(); ctx.setLineDash([]); - + ctx.fillStyle = '#ff8c6a'; ctx.font = '10px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('Increase weights for errors', width / 2, y + 80); } } - + // Title ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('Boosting: Sequential Learning from Mistakes', width / 2, 30); - + // Final ctx.fillStyle = '#ff8c6a'; ctx.font = 'bold 14px sans-serif'; ctx.fillText('Final Prediction = Weighted Combination of All Models', width / 2, height - 20); - - logViz('Ensemble Methods', 'Boosting Complete', 'success'); -} - -function drawGBLearningRate() { - // Implementation moved to Gradient Boosting section -} - -function drawGBStumps() { - // Implementation moved to Gradient Boosting section -} -function drawGBPredictions() { - // Implementation moved to Gradient Boosting section -} - -function drawXGBGain() { - // Implementation moved to XGBoost section -} - -function drawXGBRegularization() { - // Implementation moved to XGBoost section -} - -function drawXGBHessian() { - // Implementation moved to XGBoost section + logViz('Ensemble Methods', 'Boosting Complete', 'success'); } -function drawXGBLeafWeights() { - // Implementation moved to XGBoost section -} +// Stubs removed to restore original implementations -function drawXGBComparison() { - // Implementation moved to XGBoost section -} function drawRandomForestViz() { const canvas = document.getElementById('random-forest-viz'); if (!canvas) return; - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 400; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const treeY = 120; const numTrees = 5; const treeSpacing = (width - 100) / numTrees; const treeSize = 50; - + // Original data ctx.fillStyle = '#6aa9ff33'; ctx.fillRect(width / 2 - 100, 40, 200, 50); @@ -5454,11 +5915,11 @@ function drawRandomForestViz() { ctx.font = 'bold 14px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('Training Data', width / 2, 70); - + // Trees for (let i = 0; i < numTrees; i++) { const x = 50 + i * treeSpacing + treeSpacing / 2; - + // Arrow from data ctx.strokeStyle = '#7ef0d4'; ctx.lineWidth = 1; @@ -5466,7 +5927,7 @@ function drawRandomForestViz() { ctx.moveTo(width / 2, 90); ctx.lineTo(x, treeY - 20); ctx.stroke(); - + // Tree icon (triangle) ctx.fillStyle = '#7ef0d4'; ctx.beginPath(); @@ -5475,17 +5936,17 @@ function drawRandomForestViz() { ctx.lineTo(x + treeSize / 2, treeY + treeSize - 20); ctx.closePath(); ctx.fill(); - + // Trunk ctx.fillStyle = '#ffb490'; ctx.fillRect(x - 8, treeY + treeSize - 20, 16, 30); - + // Tree label ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 11px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(`Tree ${i + 1}`, x, treeY + treeSize + 25); - + // Random features note if (i === 0) { ctx.font = '9px sans-serif'; @@ -5493,18 +5954,18 @@ function drawRandomForestViz() { ctx.fillText('Random', x, treeY + treeSize + 40); ctx.fillText('subset', x, treeY + treeSize + 52); } - + // Prediction const predY = treeY + treeSize + 70; ctx.fillStyle = i < 3 ? '#ff8c6a' : '#7ef0d4'; ctx.beginPath(); ctx.arc(x, predY, 12, 0, 2 * Math.PI); ctx.fill(); - + ctx.fillStyle = '#1a2332'; ctx.font = 'bold 10px sans-serif'; ctx.fillText(i < 3 ? '1' : '0', x, predY + 4); - + // Arrow to vote ctx.strokeStyle = i < 3 ? '#ff8c6a' : '#7ef0d4'; ctx.lineWidth = 2; @@ -5513,14 +5974,14 @@ function drawRandomForestViz() { ctx.lineTo(width / 2, height - 80); ctx.stroke(); } - + // Vote box ctx.fillStyle = '#7ef0d433'; ctx.fillRect(width / 2 - 80, height - 80, 160, 60); ctx.strokeStyle = '#7ef0d4'; ctx.lineWidth = 3; ctx.strokeRect(width / 2 - 80, height - 80, 160, 60); - + ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 14px sans-serif'; ctx.textAlign = 'center'; @@ -5528,12 +5989,12 @@ function drawRandomForestViz() { ctx.font = 'bold 16px sans-serif'; ctx.fillStyle = '#ff8c6a'; ctx.fillText('Class 1 wins (3 vs 2)', width / 2, height - 35); - + // Title ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; ctx.fillText('Random Forest: Ensemble of Decision Trees', width / 2, 25); - + logViz('Ensemble Methods', 'Bagging Viz', 'success'); } @@ -5542,17 +6003,8 @@ let kmeansVizChart = null; let kmeansElbowChart = null; function initKMeans() { - const canvas1 = document.getElementById('kmeans-viz-canvas'); - if (canvas1 && !canvas1.dataset.initialized) { - canvas1.dataset.initialized = 'true'; - drawKMeansVisualization(); - } - - const canvas2 = document.getElementById('kmeans-elbow-canvas'); - if (canvas2 && !canvas2.dataset.initialized) { - canvas2.dataset.initialized = 'true'; - drawKMeansElbow(); - } + ensureCanvasVisible('kmeans-viz-canvas', drawKMeansVisualization); + ensureCanvasVisible('kmeans-elbow-canvas', drawKMeansElbow); } function drawKMeansVisualization() { @@ -5561,39 +6013,39 @@ function drawKMeansVisualization() { logViz('K-means', 'Scatter + Centroids', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 450; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; - + const xMin = 0, xMax = 10, yMin = 0, yMax = 12; const scaleX = (x) => padding + (x / xMax) * chartWidth; const scaleY = (y) => height - padding - (y / yMax) * chartHeight; - + // Data points const points = [ - {id: 'A', x: 1, y: 2, cluster: 1}, - {id: 'B', x: 1.5, y: 1.8, cluster: 1}, - {id: 'C', x: 5, y: 8, cluster: 2}, - {id: 'D', x: 8, y: 8, cluster: 2}, - {id: 'E', x: 1, y: 0.6, cluster: 1}, - {id: 'F', x: 9, y: 11, cluster: 2} + { id: 'A', x: 1, y: 2, cluster: 1 }, + { id: 'B', x: 1.5, y: 1.8, cluster: 1 }, + { id: 'C', x: 5, y: 8, cluster: 2 }, + { id: 'D', x: 8, y: 8, cluster: 2 }, + { id: 'E', x: 1, y: 0.6, cluster: 1 }, + { id: 'F', x: 9, y: 11, cluster: 2 } ]; - + // Final centroids const centroids = [ - {x: 1.17, y: 1.47, color: '#7ef0d4'}, - {x: 7.33, y: 9.0, color: '#ff8c6a'} + { x: 1.17, y: 1.47, color: '#7ef0d4' }, + { x: 7.33, y: 9.0, color: '#ff8c6a' } ]; - + // Draw lines from points to centroids points.forEach(p => { const c = centroids[p.cluster - 1]; @@ -5604,7 +6056,7 @@ function drawKMeansVisualization() { ctx.lineTo(scaleX(c.x), scaleY(c.y)); ctx.stroke(); }); - + // Draw points points.forEach(p => { ctx.fillStyle = p.cluster === 1 ? '#7ef0d4' : '#ff8c6a'; @@ -5614,14 +6066,14 @@ function drawKMeansVisualization() { ctx.strokeStyle = '#1a2332'; ctx.lineWidth = 2; ctx.stroke(); - + // Label ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 12px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(p.id, scaleX(p.x), scaleY(p.y) - 15); }); - + // Draw centroids centroids.forEach((c, i) => { ctx.fillStyle = c.color; @@ -5631,7 +6083,7 @@ function drawKMeansVisualization() { ctx.strokeStyle = '#e8eef6'; ctx.lineWidth = 3; ctx.stroke(); - + // Draw X ctx.strokeStyle = '#1a2332'; ctx.lineWidth = 2; @@ -5641,14 +6093,14 @@ function drawKMeansVisualization() { ctx.moveTo(scaleX(c.x) + 6, scaleY(c.y) - 6); ctx.lineTo(scaleX(c.x) - 6, scaleY(c.y) + 6); ctx.stroke(); - + // Label ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 13px sans-serif'; ctx.textAlign = 'center'; - ctx.fillText(`c${i+1}`, scaleX(c.x), scaleY(c.y) + 25); + ctx.fillText(`c${i + 1}`, scaleX(c.x), scaleY(c.y) + 25); }); - + // Axes ctx.strokeStyle = '#2a3544'; ctx.lineWidth = 2; @@ -5657,7 +6109,7 @@ function drawKMeansVisualization() { ctx.lineTo(padding, height - padding); ctx.lineTo(width - padding, height - padding); ctx.stroke(); - + // Labels ctx.fillStyle = '#a9b4c2'; ctx.font = '12px sans-serif'; @@ -5668,35 +6120,35 @@ function drawKMeansVisualization() { ctx.rotate(-Math.PI / 2); ctx.fillText('Y', 0, 0); ctx.restore(); - + // Title ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('K-means Clustering (K=2) - Final State', width / 2, 30); - + // WCSS ctx.fillStyle = '#6aa9ff'; ctx.font = '14px sans-serif'; ctx.textAlign = 'left'; ctx.fillText('WCSS = 15.984', padding, height - padding + 30); - + logViz('K-means', 'Scatter + Centroids', 'success'); } function drawKMeansElbow() { const canvas = document.getElementById('kmeans-elbow-canvas'); if (!canvas) return; - + if (kmeansElbowChart) { kmeansElbowChart.destroy(); } - + const ctx = canvas.getContext('2d'); - + const kValues = [1, 2, 3, 4, 5]; const wcssValues = [50, 18, 10, 8, 7]; - + kmeansElbowChart = createVerifiedVisualization('kmeans-elbow-canvas', { type: 'line', data: { @@ -5852,10 +6304,15 @@ let comparisonState = { }; function initAlgorithmComparison() { + const cvs = document.getElementById('radar-comparison-canvas'); + if (cvs && cvs.offsetWidth === 0) { + setTimeout(initAlgorithmComparison, 100); + return; + } const container = document.getElementById('algorithm-checkboxes'); if (!container || container.dataset.initialized) return; container.dataset.initialized = 'true'; - + populateAlgorithmCheckboxes(); initComparisonListeners(); initQuiz(); @@ -5864,21 +6321,21 @@ function initAlgorithmComparison() { function populateAlgorithmCheckboxes() { const container = document.getElementById('algorithm-checkboxes'); if (!container) return; - + const categoryRadios = document.querySelectorAll('input[name="category"]'); - + function updateCheckboxes() { const selectedCategory = document.querySelector('input[name="category"]:checked')?.value || 'all'; container.innerHTML = ''; - + Object.keys(comparisonState.algorithmData).forEach(name => { const algo = comparisonState.algorithmData[name]; const category = algo.category.toLowerCase(); - - if (selectedCategory === 'all' || - (selectedCategory === 'supervised' && category.includes('supervised')) || - (selectedCategory === 'unsupervised' && category.includes('unsupervised'))) { - + + if (selectedCategory === 'all' || + (selectedCategory === 'supervised' && category.includes('supervised')) || + (selectedCategory === 'unsupervised' && category.includes('unsupervised'))) { + const label = document.createElement('label'); label.style.display = 'flex'; label.style.alignItems = 'center'; @@ -5887,24 +6344,24 @@ function populateAlgorithmCheckboxes() { label.style.padding = '8px'; label.style.borderRadius = '6px'; label.style.transition = 'background 0.2s'; - + const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.value = name; checkbox.addEventListener('change', updateSelection); - + const text = document.createTextNode(name); - + label.appendChild(checkbox); label.appendChild(text); label.addEventListener('mouseenter', () => label.style.background = 'var(--color-secondary)'); label.addEventListener('mouseleave', () => label.style.background = 'transparent'); - + container.appendChild(label); } }); } - + categoryRadios.forEach(radio => radio.addEventListener('change', updateCheckboxes)); updateCheckboxes(); } @@ -5912,16 +6369,16 @@ function populateAlgorithmCheckboxes() { function updateSelection() { const checkboxes = document.querySelectorAll('#algorithm-checkboxes input[type="checkbox"]:checked'); comparisonState.selectedAlgorithms = Array.from(checkboxes).map(cb => cb.value); - + const count = comparisonState.selectedAlgorithms.length; const countEl = document.getElementById('selection-count'); const compareBtn = document.getElementById('compare-btn'); - + if (countEl) { countEl.textContent = `Selected: ${count} algorithm${count !== 1 ? 's' : ''}`; countEl.style.color = count >= 2 && count <= 5 ? 'var(--color-success)' : 'var(--color-error)'; } - + if (compareBtn) { compareBtn.disabled = count < 2 || count > 5; } @@ -5932,13 +6389,13 @@ function initComparisonListeners() { if (compareBtn) { compareBtn.addEventListener('click', showComparison); } - + const viewBtns = document.querySelectorAll('.view-btn'); viewBtns.forEach(btn => { btn.addEventListener('click', () => { viewBtns.forEach(b => b.classList.remove('active')); btn.classList.add('active'); - + const view = btn.dataset.view; document.querySelectorAll('.comparison-view').forEach(v => v.style.display = 'none'); const targetView = document.getElementById(`view-${view}`); @@ -5950,10 +6407,10 @@ function initComparisonListeners() { function showComparison() { const resultsDiv = document.getElementById('comparison-results'); if (!resultsDiv) return; - + resultsDiv.style.display = 'block'; resultsDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); - + renderComparisonTable(); renderRadarChart(); renderHeatmap(); @@ -5964,7 +6421,7 @@ function showComparison() { function renderComparisonTable() { const table = document.getElementById('comparison-table'); if (!table) return; - + const metrics = [ { key: 'speed', label: 'Speed', format: (v) => '⭐'.repeat(v) }, { key: 'accuracy', label: 'Accuracy', format: (v) => '⭐'.repeat(v) }, @@ -5976,13 +6433,13 @@ function renderComparisonTable() { { key: 'memoryUsage', label: 'Memory Usage' }, { key: 'bestFor', label: 'Best For' } ]; - + let html = 'Metric'; comparisonState.selectedAlgorithms.forEach(name => { html += `${name}`; }); html += ''; - + metrics.forEach(metric => { html += `${metric.label}`; comparisonState.selectedAlgorithms.forEach(name => { @@ -5993,10 +6450,10 @@ function renderComparisonTable() { }); html += ''; }); - + html += ''; table.innerHTML = html; - + logViz('Algorithm Comparison', 'Comparison Table', 'success'); } @@ -6005,18 +6462,18 @@ let radarComparisonChart = null; function renderRadarChart() { const canvas = document.getElementById('radar-comparison-canvas'); if (!canvas) return; - + if (radarComparisonChart) { radarComparisonChart.destroy(); radarComparisonChart = null; } - + const ctx = canvas.getContext('2d'); - canvas.width = canvas.offsetWidth; + canvas.width = canvas.offsetWidth || 600; canvas.height = 500; - + const colors = ['#6aa9ff', '#7ef0d4', '#ff8c6a', '#ffeb3b', '#ffb490']; - + const datasets = comparisonState.selectedAlgorithms.map((name, i) => { const algo = comparisonState.algorithmData[name]; return { @@ -6028,12 +6485,12 @@ function renderRadarChart() { pointRadius: 4 }; }); - + if (radarComparisonChart) { radarComparisonChart.destroy(); radarComparisonChart = null; } - + radarComparisonChart = createVerifiedVisualization('radar-comparison-canvas', { type: 'radar', data: { @@ -6065,11 +6522,11 @@ function renderRadarChart() { function renderHeatmap() { const container = document.getElementById('view-heatmap'); if (!container) return; - + // Remove canvas, use HTML table instead for 100% browser compatibility const metrics = ['Speed', 'Accuracy', 'Data Efficiency', 'Interpretability', 'Scalability']; const algos = comparisonState.selectedAlgorithms; - + // Helper function to get color based on value function getHeatmapColor(value) { const intensity = value / 5; @@ -6078,12 +6535,12 @@ function renderHeatmap() { const b = Math.floor(106 + 106 * intensity); return `rgb(${r}, ${g}, ${b})`; } - + // Build HTML table heatmap let html = '

Performance Heatmap (Higher is Better)

'; html += '
'; html += ''; - + // Header row html += ''; html += ''; @@ -6091,16 +6548,16 @@ function renderHeatmap() { html += ``; }); html += ''; - + // Data rows html += ''; algos.forEach((name, i) => { const algo = comparisonState.algorithmData[name]; const values = [algo.speed, algo.accuracy, 5 - algo.dataRequired, algo.interpretability, algo.scalability]; - + html += ``; html += ``; - + values.forEach((value, j) => { const color = getHeatmapColor(value); const stars = '⭐'.repeat(Math.round(value)); @@ -6114,9 +6571,9 @@ function renderHeatmap() { html += ''; html += '
Algorithm${metric}
${name}
'; html += '
'; - + logViz('Algorithm Comparison', 'Heatmap', 'success'); - + // Legend html += '
'; html += 'Legend: '; @@ -6124,7 +6581,7 @@ function renderHeatmap() { html += '🟡 Medium (3) '; html += '🟢 High (4-5)'; html += '
'; - + // Find the canvas and replace with our HTML const oldCanvas = container.querySelector('#heatmap-canvas'); if (oldCanvas) { @@ -6137,7 +6594,7 @@ function renderHeatmap() { function renderUseCaseMatrix() { const table = document.getElementById('matrix-table'); if (!table) return; - + const useCases = [ { key: 'regression', label: 'Regression' }, { key: 'classification', label: 'Classification' }, @@ -6145,13 +6602,13 @@ function renderUseCaseMatrix() { { key: 'speed', label: 'Speed' }, { key: 'interpretability', label: 'Interpretability' } ]; - + let html = 'Use Case'; comparisonState.selectedAlgorithms.forEach(name => { html += `${name}`; }); html += ''; - + useCases.forEach(useCase => { html += `${useCase.label}`; comparisonState.selectedAlgorithms.forEach(name => { @@ -6162,20 +6619,20 @@ function renderUseCaseMatrix() { }); html += ''; }); - + html += ''; table.innerHTML = html; - + logViz('Algorithm Comparison', 'Use Case Matrix', 'success'); } function renderDetailedCards() { const container = document.getElementById('detailed-cards'); if (!container) return; - + let html = '

Detailed Comparison

'; html += '
'; - + comparisonState.selectedAlgorithms.forEach(name => { const algo = comparisonState.algorithmData[name]; html += ` @@ -6203,17 +6660,17 @@ function renderDetailedCards() {
`; }); - + html += ''; container.innerHTML = html; - + logViz('Algorithm Comparison', 'Detailed Cards', 'success'); } function initQuiz() { const questions = document.querySelectorAll('.quiz-question'); const resultDiv = document.getElementById('quiz-result'); - + questions.forEach((q, idx) => { const radios = q.querySelectorAll('input[type="radio"]'); radios.forEach(radio => { @@ -6221,23 +6678,23 @@ function initQuiz() { if (idx < questions.length - 1) { questions[idx + 1].style.display = 'block'; } - + if (idx === questions.length - 1) { showQuizResult(); } }); }); }); - + function showQuizResult() { const q1 = document.querySelector('input[name="q1"]:checked')?.value; const q2 = document.querySelector('input[name="q2"]:checked')?.value; const q3 = document.querySelector('input[name="q3"]:checked')?.value; const q4 = document.querySelector('input[name="q4"]:checked')?.value; - + let recommendation = ''; let alternatives = []; - + if (q1 === 'no') { recommendation = 'K-means'; alternatives = ['PCA', 'DBSCAN']; @@ -6264,7 +6721,7 @@ function initQuiz() { recommendation = 'K-means'; alternatives = ['PCA']; } - + if (resultDiv) { resultDiv.style.display = 'block'; resultDiv.innerHTML = ` @@ -6283,34 +6740,34 @@ function initQuiz() { function drawDecisionFlowchart() { const canvas = document.getElementById('decision-flowchart'); if (!canvas) return; - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 500; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const nodes = [ - { x: width/2, y: 50, text: 'Start:\nWhat problem?', w: 140, h: 60, color: '#7ef0d4', type: 'start' }, - { x: width/4, y: 160, text: 'Classification', w: 120, h: 50, color: '#6aa9ff', type: 'decision' }, - { x: width/2, y: 160, text: 'Regression', w: 120, h: 50, color: '#6aa9ff', type: 'decision' }, - { x: 3*width/4, y: 160, text: 'Clustering', w: 120, h: 50, color: '#6aa9ff', type: 'decision' }, - { x: width/8, y: 270, text: 'Linear?', w: 100, h: 50, color: '#ffb490', type: 'question' }, - { x: 3*width/8, y: 270, text: 'Fast?', w: 100, h: 50, color: '#ffb490', type: 'question' }, - { x: width/2, y: 270, text: 'Linear?', w: 100, h: 50, color: '#ffb490', type: 'question' }, - { x: 3*width/4, y: 270, text: 'Known K?', w: 100, h: 50, color: '#ffb490', type: 'question' }, - { x: width/16, y: 380, text: 'Logistic\nRegression', w: 90, h: 50, color: '#7ef0d4', type: 'result' }, - { x: 3*width/16, y: 380, text: 'SVM', w: 90, h: 50, color: '#7ef0d4', type: 'result' }, - { x: 5*width/16, y: 380, text: 'Naive\nBayes', w: 90, h: 50, color: '#7ef0d4', type: 'result' }, - { x: 7*width/16, y: 380, text: 'Random\nForest', w: 90, h: 50, color: '#7ef0d4', type: 'result' }, - { x: 9*width/16, y: 380, text: 'Linear\nRegression', w: 90, h: 50, color: '#7ef0d4', type: 'result' }, - { x: 11*width/16, y: 380, text: 'XGBoost', w: 90, h: 50, color: '#7ef0d4', type: 'result' }, - { x: 13*width/16, y: 380, text: 'K-means', w: 90, h: 50, color: '#7ef0d4', type: 'result' }, - { x: 15*width/16, y: 380, text: 'DBSCAN', w: 90, h: 50, color: '#7ef0d4', type: 'result' } + { x: width / 2, y: 50, text: 'Start:\nWhat problem?', w: 140, h: 60, color: '#7ef0d4', type: 'start' }, + { x: width / 4, y: 160, text: 'Classification', w: 120, h: 50, color: '#6aa9ff', type: 'decision' }, + { x: width / 2, y: 160, text: 'Regression', w: 120, h: 50, color: '#6aa9ff', type: 'decision' }, + { x: 3 * width / 4, y: 160, text: 'Clustering', w: 120, h: 50, color: '#6aa9ff', type: 'decision' }, + { x: width / 8, y: 270, text: 'Linear?', w: 100, h: 50, color: '#ffb490', type: 'question' }, + { x: 3 * width / 8, y: 270, text: 'Fast?', w: 100, h: 50, color: '#ffb490', type: 'question' }, + { x: width / 2, y: 270, text: 'Linear?', w: 100, h: 50, color: '#ffb490', type: 'question' }, + { x: 3 * width / 4, y: 270, text: 'Known K?', w: 100, h: 50, color: '#ffb490', type: 'question' }, + { x: width / 16, y: 380, text: 'Logistic\nRegression', w: 90, h: 50, color: '#7ef0d4', type: 'result' }, + { x: 3 * width / 16, y: 380, text: 'SVM', w: 90, h: 50, color: '#7ef0d4', type: 'result' }, + { x: 5 * width / 16, y: 380, text: 'Naive\nBayes', w: 90, h: 50, color: '#7ef0d4', type: 'result' }, + { x: 7 * width / 16, y: 380, text: 'Random\nForest', w: 90, h: 50, color: '#7ef0d4', type: 'result' }, + { x: 9 * width / 16, y: 380, text: 'Linear\nRegression', w: 90, h: 50, color: '#7ef0d4', type: 'result' }, + { x: 11 * width / 16, y: 380, text: 'XGBoost', w: 90, h: 50, color: '#7ef0d4', type: 'result' }, + { x: 13 * width / 16, y: 380, text: 'K-means', w: 90, h: 50, color: '#7ef0d4', type: 'result' }, + { x: 15 * width / 16, y: 380, text: 'DBSCAN', w: 90, h: 50, color: '#7ef0d4', type: 'result' } ]; - + const edges = [ { from: 0, to: 1 }, { from: 0, to: 2 }, { from: 0, to: 3 }, { from: 1, to: 4 }, { from: 1, to: 5 }, @@ -6321,19 +6778,19 @@ function drawDecisionFlowchart() { { from: 6, to: 12, label: 'Yes' }, { from: 6, to: 13, label: 'No' }, { from: 7, to: 14, label: 'Yes' }, { from: 7, to: 15, label: 'No' } ]; - + // Draw edges ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 2; edges.forEach(edge => { const from = nodes[edge.from]; const to = nodes[edge.to]; - + ctx.beginPath(); - ctx.moveTo(from.x, from.y + from.h/2); - ctx.lineTo(to.x, to.y - to.h/2); + ctx.moveTo(from.x, from.y + from.h / 2); + ctx.lineTo(to.x, to.y - to.h / 2); ctx.stroke(); - + if (edge.label) { ctx.fillStyle = '#7ef0d4'; ctx.font = '10px sans-serif'; @@ -6343,18 +6800,18 @@ function drawDecisionFlowchart() { ctx.fillText(edge.label, midX + 12, midY); } }); - + // Draw nodes nodes.forEach(node => { - const x = node.x - node.w/2; - const y = node.y - node.h/2; - + const x = node.x - node.w / 2; + const y = node.y - node.h / 2; + ctx.fillStyle = node.color + '33'; ctx.fillRect(x, y, node.w, node.h); ctx.strokeStyle = node.color; ctx.lineWidth = 2; ctx.strokeRect(x, y, node.w, node.h); - + ctx.fillStyle = '#e8eef6'; ctx.font = node.type === 'result' ? 'bold 11px sans-serif' : '11px sans-serif'; ctx.textAlign = 'center'; @@ -6363,12 +6820,12 @@ function drawDecisionFlowchart() { ctx.fillText(line, node.x, node.y - (lines.length - 1) * 6 + i * 12); }); }); - + // Title ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center'; - ctx.fillText('Algorithm Selection Flowchart', width/2, 25); + ctx.fillText('Algorithm Selection Flowchart', width / 2, 25); } // Diagnostic Functions @@ -6383,12 +6840,12 @@ function showDiagnostics() {
  • Cookies Enabled: ${navigator.cookieEnabled ? '✓ Yes' : '✗ No'}
  • `; } - + const libraryDetails = document.getElementById('library-details'); if (libraryDetails) { const chartJsLoaded = typeof Chart !== 'undefined'; const canvasSupport = !!document.createElement('canvas').getContext('2d'); - + libraryDetails.innerHTML = `
  • Chart.js: ${chartJsLoaded ? '✓ Loaded (v' + (Chart.version || '4.x') + ')' : '✗ Missing'}
  • Canvas Support: ${canvasSupport ? '✓ Yes' : '✗ No'}
  • @@ -6396,11 +6853,11 @@ function showDiagnostics() {
  • Screen Resolution: ${window.screen.width}x${window.screen.height}
  • `; } - + const successCount = document.getElementById('diag-success-count'); const failedCount = document.getElementById('diag-failed-count'); const warningCount = document.getElementById('diag-warning-count'); - + if (successCount) successCount.textContent = vizLog.success.length; if (failedCount) failedCount.textContent = vizLog.failed.length; if (warningCount) warningCount.textContent = vizLog.warnings.length; @@ -6409,25 +6866,25 @@ function showDiagnostics() { function showDiagnosticDetails(filter) { const container = document.getElementById('viz-details'); if (!container) return; - + let items = []; if (filter === 'success') items = vizLog.success; else if (filter === 'failed') items = vizLog.failed; else items = [...vizLog.success, ...vizLog.failed, ...vizLog.warnings]; - + if (items.length === 0) { container.innerHTML = '

    No items to display

    '; return; } - + let html = ''; html += ''; html += ''; - + items.forEach(item => { const statusIcon = item.status === 'success' ? '✓' : (item.status === 'failed' ? '✗' : '⚠'); const statusColor = item.status === 'success' ? 'var(--color-success)' : (item.status === 'failed' ? 'var(--color-error)' : 'var(--color-warning)'); - + html += ``; html += ``; html += ``; @@ -6435,36 +6892,28 @@ function showDiagnosticDetails(filter) { html += ``; html += ``; }); - + html += '
    ModuleVisualizationStatusTime
    ${item.module}${item.name}${item.timestamp}
    '; container.innerHTML = html; } // NEW VISUALIZATIONS FOR ADDED TOPICS +// Gradient Boosting Classification // Gradient Boosting Classification function initGradientBoostingClassification() { - const canvas1 = document.getElementById('gb-class-sequential-canvas'); - if (canvas1 && !canvas1.dataset.initialized) { - canvas1.dataset.initialized = 'true'; - drawGBClassSequential(); - } - - const canvas2 = document.getElementById('gb-class-gradients-canvas'); - if (canvas2 && !canvas2.dataset.initialized) { - canvas2.dataset.initialized = 'true'; - drawGBClassGradients(); - } + ensureCanvasVisible('gb-class-sequential-canvas', drawGBClassSequential); + ensureCanvasVisible('gb-class-gradients-canvas', drawGBClassGradients); } function drawGBClassSequential() { const canvas = document.getElementById('gb-class-sequential-canvas'); if (!canvas) return; - + const iterations = [0, 1, 2, 3, 4, 5, 10]; const house1 = [0.4, 0.39, 0.37, 0.35, 0.33, 0.31, 0.22]; const house4 = [0.4, 0.43, 0.47, 0.52, 0.57, 0.62, 0.78]; - + createVerifiedVisualization('gb-class-sequential-canvas', { type: 'line', data: { @@ -6521,7 +6970,7 @@ function drawGBClassSequential() { function drawGBClassGradients() { const canvas = document.getElementById('gb-class-gradients-canvas'); if (!canvas) return; - + createVerifiedVisualization('gb-class-gradients-canvas', { type: 'bar', data: { @@ -6568,21 +7017,17 @@ function drawGBClassGradients() { // XGBoost Classification function initXGBoostClassification() { - const canvas = document.getElementById('xgb-class-hessian-canvas'); - if (canvas && !canvas.dataset.initialized) { - canvas.dataset.initialized = 'true'; - drawXGBClassHessian(); - } + ensureCanvasVisible('xgb-class-hessian-canvas', drawXGBClassHessian); } function drawXGBClassHessian() { const canvas = document.getElementById('xgb-class-hessian-canvas'); if (!canvas) return; - + const houses = ['House 1', 'House 2', 'House 3', 'House 4', 'House 5']; const gradients = [0.4, 0.4, 0.4, -0.6, -0.6]; const hessians = [0.24, 0.24, 0.24, 0.24, 0.24]; - + createVerifiedVisualization('xgb-class-hessian-canvas', { type: 'bar', data: { @@ -6642,6 +7087,10 @@ function drawXGBClassHessian() { function initHierarchicalClustering() { const canvas = document.getElementById('hierarchical-dendrogram-canvas'); if (canvas && !canvas.dataset.initialized) { + if (canvas.offsetWidth === 0) { + setTimeout(initHierarchicalClustering, 100); + return; + } canvas.dataset.initialized = 'true'; drawHierarchicalDendrogram(); } @@ -6653,36 +7102,36 @@ function drawHierarchicalDendrogram() { logViz('Hierarchical Clustering', 'Dendrogram', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 450; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 60; const numPoints = 6; const pointSpacing = (width - 2 * padding) / numPoints; const labels = ['A', 'B', 'C', 'D', 'E', 'F']; - + // Draw points at bottom const pointY = height - 40; labels.forEach((label, i) => { const x = padding + i * pointSpacing + pointSpacing / 2; - + ctx.fillStyle = '#7ef0d4'; ctx.beginPath(); ctx.arc(x, pointY, 6, 0, 2 * Math.PI); ctx.fill(); - + ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 12px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(label, x, pointY + 20); }); - + // Draw dendrogram merges const merges = [ { points: [0, 1], height: 320 }, @@ -6691,10 +7140,10 @@ function drawHierarchicalDendrogram() { { points: [3, 4, 5], height: 200 }, { points: [0, 1, 2, 3, 4, 5], height: 80 } ]; - + ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 2; - + // Merge A-B let x1 = padding + 0 * pointSpacing + pointSpacing / 2; let x2 = padding + 1 * pointSpacing + pointSpacing / 2; @@ -6704,7 +7153,7 @@ function drawHierarchicalDendrogram() { ctx.lineTo(x2, merges[0].height); ctx.lineTo(x2, pointY); ctx.stroke(); - + // Merge D-E x1 = padding + 3 * pointSpacing + pointSpacing / 2; x2 = padding + 4 * pointSpacing + pointSpacing / 2; @@ -6714,7 +7163,7 @@ function drawHierarchicalDendrogram() { ctx.lineTo(x2, merges[1].height); ctx.lineTo(x2, pointY); ctx.stroke(); - + // Merge (A-B)-C x1 = padding + 0.5 * pointSpacing + pointSpacing / 2; x2 = padding + 2 * pointSpacing + pointSpacing / 2; @@ -6724,7 +7173,7 @@ function drawHierarchicalDendrogram() { ctx.lineTo(x2, merges[2].height); ctx.lineTo(x2, pointY); ctx.stroke(); - + // Merge (D-E)-F x1 = padding + 3.5 * pointSpacing + pointSpacing / 2; x2 = padding + 5 * pointSpacing + pointSpacing / 2; @@ -6734,7 +7183,7 @@ function drawHierarchicalDendrogram() { ctx.lineTo(x2, merges[3].height); ctx.lineTo(x2, pointY); ctx.stroke(); - + // Final merge x1 = padding + 1.5 * pointSpacing; x2 = padding + 4.5 * pointSpacing; @@ -6744,13 +7193,13 @@ function drawHierarchicalDendrogram() { ctx.lineTo(x2, merges[4].height); ctx.lineTo(x2, merges[3].height); ctx.stroke(); - + // Title ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('Dendrogram: Cluster Merging History', width / 2, 30); - + // Y-axis label ctx.fillStyle = '#a9b4c2'; ctx.font = '12px sans-serif'; @@ -6759,7 +7208,7 @@ function drawHierarchicalDendrogram() { ctx.rotate(-Math.PI / 2); ctx.fillText('Distance', 0, 0); ctx.restore(); - + logViz('Hierarchical Clustering', 'Dendrogram', 'success'); } @@ -6767,6 +7216,10 @@ function drawHierarchicalDendrogram() { function initDBSCAN() { const canvas = document.getElementById('dbscan-clusters-canvas'); if (canvas && !canvas.dataset.initialized) { + if (canvas.offsetWidth === 0) { + setTimeout(initDBSCAN, 100); + return; + } canvas.dataset.initialized = 'true'; drawDBSCANClusters(); } @@ -6778,34 +7231,34 @@ function drawDBSCANClusters() { logViz('DBSCAN', 'Clusters Visualization', 'failed', 'Canvas not found'); return; } - + const ctx = canvas.getContext('2d'); - const width = canvas.width = canvas.offsetWidth; + const width = canvas.width = canvas.offsetWidth || 600; const height = canvas.height = 450; - + ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); - + const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; - + const scaleX = (x) => padding + (x / 10) * chartWidth; const scaleY = (y) => height - padding - (y / 10) * chartHeight; - + const eps = 1.5; const epsPixels = (eps / 10) * chartWidth; - + // Core points (cluster 1) - const core1 = [{x: 1, y: 1}, {x: 1.2, y: 1.5}, {x: 1.5, y: 1.2}]; + const core1 = [{ x: 1, y: 1 }, { x: 1.2, y: 1.5 }, { x: 1.5, y: 1.2 }]; // Core points (cluster 2) - const core2 = [{x: 8, y: 8}, {x: 8.2, y: 8.5}, {x: 8.5, y: 8.2}]; + const core2 = [{ x: 8, y: 8 }, { x: 8.2, y: 8.5 }, { x: 8.5, y: 8.2 }]; // Border points - const border = [{x: 2.2, y: 2}]; + const border = [{ x: 2.2, y: 2 }]; // Outliers - const outliers = [{x: 5, y: 5}, {x: 4.5, y: 6}]; - + const outliers = [{ x: 5, y: 5 }, { x: 4.5, y: 6 }]; + // Draw eps circles around core points ctx.strokeStyle = 'rgba(126, 240, 212, 0.3)'; ctx.lineWidth = 1; @@ -6816,7 +7269,7 @@ function drawDBSCANClusters() { ctx.stroke(); }); ctx.setLineDash([]); - + // Draw core points core1.forEach(p => { ctx.fillStyle = '#7ef0d4'; @@ -6827,7 +7280,7 @@ function drawDBSCANClusters() { ctx.lineWidth = 2; ctx.stroke(); }); - + core2.forEach(p => { ctx.fillStyle = '#6aa9ff'; ctx.beginPath(); @@ -6837,7 +7290,7 @@ function drawDBSCANClusters() { ctx.lineWidth = 2; ctx.stroke(); }); - + // Draw border points border.forEach(p => { ctx.fillStyle = '#ffb490'; @@ -6845,7 +7298,7 @@ function drawDBSCANClusters() { ctx.arc(scaleX(p.x), scaleY(p.y), 8, 0, 2 * Math.PI); ctx.fill(); }); - + // Draw outliers outliers.forEach(p => { ctx.strokeStyle = '#ff8c6a'; @@ -6854,7 +7307,7 @@ function drawDBSCANClusters() { ctx.arc(scaleX(p.x), scaleY(p.y), 8, 0, 2 * Math.PI); ctx.stroke(); }); - + // Legend ctx.fillStyle = '#7ef0d4'; ctx.beginPath(); @@ -6864,14 +7317,14 @@ function drawDBSCANClusters() { ctx.font = '12px sans-serif'; ctx.textAlign = 'left'; ctx.fillText('Core points', padding + 35, 35); - + ctx.fillStyle = '#ffb490'; ctx.beginPath(); ctx.arc(padding + 140, 30, 8, 0, 2 * Math.PI); ctx.fill(); ctx.fillStyle = '#e8eef6'; ctx.fillText('Border points', padding + 155, 35); - + ctx.strokeStyle = '#ff8c6a'; ctx.lineWidth = 3; ctx.beginPath(); @@ -6879,24 +7332,29 @@ function drawDBSCANClusters() { ctx.stroke(); ctx.fillStyle = '#e8eef6'; ctx.fillText('Outliers', padding + 285, 35); - + // Title ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('DBSCAN: Core, Border, and Outlier Points', width / 2, height - 10); - + logViz('DBSCAN', 'Clusters Visualization', 'success'); } // Clustering Evaluation function initClusteringEvaluation() { const canvas1 = document.getElementById('silhouette-plot-canvas'); - if (canvas1 && !canvas1.dataset.initialized) { - canvas1.dataset.initialized = 'true'; - drawSilhouettePlot(); + if (!canvas1 || canvas1.dataset.initialized) return; + + if (canvas1.offsetWidth === 0) { + setTimeout(initClusteringEvaluation, 100); + return; } - + + canvas1.dataset.initialized = 'true'; + drawSilhouettePlot(); + const canvas2 = document.getElementById('ch-index-canvas'); if (canvas2 && !canvas2.dataset.initialized) { canvas2.dataset.initialized = 'true'; @@ -6907,7 +7365,7 @@ function initClusteringEvaluation() { function drawSilhouettePlot() { const canvas = document.getElementById('silhouette-plot-canvas'); if (!canvas) return; - + createVerifiedVisualization('silhouette-plot-canvas', { type: 'bar', data: { @@ -6953,10 +7411,10 @@ function drawSilhouettePlot() { function drawCHIndex() { const canvas = document.getElementById('ch-index-canvas'); if (!canvas) return; - + const kValues = [2, 3, 4, 5, 6, 7, 8]; const chScores = [89, 234, 187, 145, 112, 95, 78]; - + createVerifiedVisualization('ch-index-canvas', { type: 'line', data: {