// Get canvas and context const canvas = document.getElementById('regressionCanvas'); const ctx = canvas.getContext('2d'); // Data from your Python script (X, y) // These are hardcoded here for visualization purposes. // In a real advanced app, these might be dynamically loaded. const X_data = [1, 2, 3, 4, 5]; const y_data = [35, 45, 55, 65, 75]; // --- Understanding Slope (m) and Intercept (b) --- // For a perfect linear relationship as in your data, // we can manually calculate slope (m) and intercept (b). // In a real-world scenario with scattered data, the scikit-learn // LinearRegression model uses more advanced statistical methods // (like Ordinary Least Squares) to find the 'best fit' line // that minimizes the squared differences between actual and predicted y values. // Calculate Slope (m): // m = (y2 - y1) / (x2 - x1) // Using points (1, 35) and (2, 45): // m = (45 - 35) / (2 - 1) = 10 / 1 = 10 const slope = 10; // Calculate Intercept (b): // b = y - m * x // Using point (1, 35) and calculated slope m=10: // b = 35 - (10 * 1) = 35 - 10 = 25 const intercept = 25; // Display slope and intercept values in the HTML document.getElementById('slopeValue').textContent = slope.toFixed(2); document.getElementById('interceptValue').textContent = intercept.toFixed(2); // Canvas dimensions and padding let canvasWidth, canvasHeight; const padding = 50; // Scale factors for drawing data onto the canvas let xScale, yScale; let xMin, xMax, yMin, yMax; // Prediction variables (these will be updated when the user inputs hours) let predictedHours = null; let predictedScore = null; // Function to set up scaling based on data range and canvas size function setupScaling() { canvasWidth = canvas.width; canvasHeight = canvas.height; // Determine data ranges for X and Y axes xMin = Math.min(...X_data, 0); // Always start X-axis at 0 // Set xMax to at least 10 (as per the last request) and ensure it covers any new predicted hours xMax = Math.max(...X_data, predictedHours !== null ? predictedHours : 0, 10) + 1; // Extend x-axis slightly beyond 10 yMin = Math.min(...y_data, 0); // Always start Y-axis at 0 // Calculate the predicted score for the determined xMax to ensure the y-axis covers the line const maxPredictedY = slope * xMax + intercept; yMax = Math.max(...y_data, predictedScore !== null ? predictedScore : 0, maxPredictedY) + 20; // Extend y-axis slightly beyond max needed // Calculate scaling factors to fit data within the canvas padding xScale = (canvasWidth - 2 * padding) / (xMax - xMin); yScale = (canvasHeight - 2 * padding) / (yMax - yMin); } // Convert data coordinates (e.g., hours, score) to canvas pixel coordinates function toCanvasX(x) { return padding + (x - xMin) * xScale; } function toCanvasY(y) { return canvasHeight - padding - (y - yMin) * yScale; } // Function to draw the entire graph, including data points, regression line, and predictions function drawGraph() { ctx.clearRect(0, 0, canvasWidth, canvasHeight); // Clear the entire canvas // Draw axes ctx.beginPath(); ctx.strokeStyle = '#64748b'; // Slate gray for axes ctx.lineWidth = 2; // X-axis (horizontal line) ctx.moveTo(padding, toCanvasY(yMin)); ctx.lineTo(canvasWidth - padding, toCanvasY(yMin)); // Y-axis (vertical line) ctx.moveTo(toCanvasX(xMin), padding); ctx.lineTo(toCanvasX(xMin), canvasHeight - padding); ctx.stroke(); // Draw axis labels and ticks ctx.fillStyle = '#475569'; // Darker gray for labels ctx.font = '14px Inter'; ctx.textAlign = 'center'; ctx.textBaseline = 'top'; // X-axis labels (Hours Studied) // Dynamic tick step for clarity on different scales const xTickStep = 1; // Every 1 hour for a graph up to 10 for (let i = Math.ceil(xMin / xTickStep) * xTickStep; i <= Math.floor(xMax); i += xTickStep) { if (i >= 0) { ctx.fillText(i + 'h', toCanvasX(i), canvasHeight - padding + 10); ctx.beginPath(); ctx.moveTo(toCanvasX(i), canvasHeight - padding); ctx.lineTo(toCanvasX(i), canvasHeight - padding - 5); ctx.stroke(); } } // X-axis title ctx.fillText('Hours Studied', canvasWidth / 2, canvasHeight - 20); ctx.textAlign = 'right'; ctx.textBaseline = 'middle'; // Y-axis labels (Score) // Dynamic tick step for clarity on different scales const yTickStep = (yMax - yMin) / 10 > 20 ? 50 : 20; // Example: every 20 or 50 points for (let i = Math.ceil(yMin / yTickStep) * yTickStep; i <= Math.floor(yMax); i += yTickStep) { if (i >= 0) { ctx.fillText(i.toFixed(0), padding - 10, toCanvasY(i)); ctx.beginPath(); ctx.moveTo(padding, toCanvasY(i)); ctx.lineTo(padding + 5, toCanvasY(i)); ctx.stroke(); } } // Y-axis title (rotated) ctx.save(); ctx.translate(20, canvasHeight / 2); ctx.rotate(-Math.PI / 2); ctx.textAlign = 'center'; ctx.fillText('Score', 0, 0); ctx.restore(); // Draw data points (blue circles) ctx.fillStyle = '#3b82f6'; // Blue for data points X_data.forEach((x, i) => { ctx.beginPath(); ctx.arc(toCanvasX(x), toCanvasY(y_data[i]), 5, 0, Math.PI * 2); // Radius 5 ctx.fill(); }); // Draw regression line (red line) ctx.beginPath(); ctx.strokeStyle = '#ef4444'; // Red for regression line ctx.lineWidth = 3; // Draw line across the entire X-axis range based on the model equation ctx.moveTo(toCanvasX(xMin), toCanvasY(slope * xMin + intercept)); ctx.lineTo(toCanvasX(xMax), toCanvasY(slope * xMax + intercept)); ctx.stroke(); // Draw predicted point and lines if available (green point and dashed lines) if (predictedHours !== null && predictedScore !== null) { const predX = toCanvasX(predictedHours); const predY = toCanvasY(predictedScore); // Predicted point ctx.fillStyle = '#22c55e'; // Green for predicted point ctx.beginPath(); ctx.arc(predX, predY, 6, 0, Math.PI * 2); // Slightly larger radius ctx.fill(); // Dotted lines to axes ctx.strokeStyle = '#22c55e'; // Green for dotted lines ctx.lineWidth = 1.5; ctx.setLineDash([5, 5]); // Dotted line style // Line from predicted point to X-axis ctx.beginPath(); ctx.moveTo(predX, predY); ctx.lineTo(predX, toCanvasY(yMin)); ctx.stroke(); // Line from predicted point to Y-axis ctx.beginPath(); ctx.moveTo(predX, predY); ctx.lineTo(toCanvasX(xMin), predY); ctx.stroke(); ctx.setLineDash([]); // Reset line dash to solid for subsequent drawings } } // Event listener for the "Predict Score" button click document.getElementById('predictBtn').addEventListener('click', () => { // Get the value from the input field and parse it as a floating-point number const hoursInput = parseFloat(document.getElementById('hoursInput').value); // Check if the input is a valid number if (!isNaN(hoursInput)) { // Update global prediction variables predictedHours = hoursInput; predictedScore = slope * predictedHours + intercept; // Display the predicted score in the HTML document.getElementById('predictedScore').textContent = predictedScore.toFixed(2); // Make the prediction output box visible document.getElementById('predictionOutput').classList.remove('hidden'); // Recalculate scaling and redraw the graph to accommodate new prediction if it extends axes setupScaling(); drawGraph(); } else { // If input is invalid, display an error message const outputDiv = document.getElementById('predictionOutput'); outputDiv.innerHTML = '
Please enter a valid number for hours studied.
'; outputDiv.classList.remove('hidden'); } }); // Function to handle canvas resizing and redraw the graph function resizeCanvas() { // Get the device pixel ratio for sharper rendering on high-DPI screens const dpi = window.devicePixelRatio; // Get the actual rendered size of the canvas element from its CSS styles const rect = canvas.getBoundingClientRect(); // Set the internal drawing buffer size of the canvas canvas.width = rect.width * dpi; canvas.height = rect.height * dpi; // Scale the drawing context to match the DPI, ensuring crisp lines and text ctx.scale(dpi, dpi); // Re-setup scaling for data to canvas coordinates and redraw setupScaling(); drawGraph(); } // Initial setup and draw when the window loads window.addEventListener('load', () => { resizeCanvas(); // Set initial canvas size and draw // Also trigger an initial prediction for the default value in the input field const initialHours = parseFloat(document.getElementById('hoursInput').value); if (!isNaN(initialHours)) { predictedHours = initialHours; predictedScore = slope * initialHours + intercept; document.getElementById('predictedScore').textContent = predictedScore.toFixed(2); document.getElementById('predictionOutput').classList.remove('hidden'); setupScaling(); drawGraph(); } }); // Redraw the graph whenever the window is resized window.addEventListener('resize', resizeCanvas); // Optional: Allow clicking on canvas to set hours input (for quick testing) canvas.addEventListener('click', (event) => { // Get mouse click coordinates relative to the canvas const rect = canvas.getBoundingClientRect(); const mouseX = (event.clientX - rect.left) / (canvas.width / canvas.getBoundingClientRect().width); const mouseY = (event.clientY - rect.top) / (canvas.height / canvas.getBoundingClientRect().height); // Corrected this line // Convert canvas X coordinate back to data X (hours studied) const clickedHours = xMin + (mouseX - padding) / xScale; // Update the input field with the clicked hours document.getElementById('hoursInput').value = clickedHours.toFixed(1); // Trigger the prediction immediately document.getElementById('predictBtn').click(); });