deedrop1140's picture
Upload 137 files
f7c7e26 verified
// 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 = '<p class="text-red-600">Please enter a valid number for hours studied.</p>';
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();
});