|
|
|
|
|
const canvas = document.getElementById('regressionCanvas');
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const X_data = [1, 2, 3, 4, 5];
|
|
|
const y_data = [35, 45, 55, 65, 75];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const slope = 10;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const intercept = 25;
|
|
|
|
|
|
|
|
|
document.getElementById('slopeValue').textContent = slope.toFixed(2);
|
|
|
document.getElementById('interceptValue').textContent = intercept.toFixed(2);
|
|
|
|
|
|
|
|
|
let canvasWidth, canvasHeight;
|
|
|
const padding = 50;
|
|
|
|
|
|
|
|
|
let xScale, yScale;
|
|
|
let xMin, xMax, yMin, yMax;
|
|
|
|
|
|
|
|
|
let predictedHours = null;
|
|
|
let predictedScore = null;
|
|
|
|
|
|
|
|
|
function setupScaling() {
|
|
|
canvasWidth = canvas.width;
|
|
|
canvasHeight = canvas.height;
|
|
|
|
|
|
|
|
|
xMin = Math.min(...X_data, 0);
|
|
|
|
|
|
xMax = Math.max(...X_data, predictedHours !== null ? predictedHours : 0, 10) + 1;
|
|
|
|
|
|
yMin = Math.min(...y_data, 0);
|
|
|
|
|
|
const maxPredictedY = slope * xMax + intercept;
|
|
|
yMax = Math.max(...y_data, predictedScore !== null ? predictedScore : 0, maxPredictedY) + 20;
|
|
|
|
|
|
|
|
|
xScale = (canvasWidth - 2 * padding) / (xMax - xMin);
|
|
|
yScale = (canvasHeight - 2 * padding) / (yMax - yMin);
|
|
|
}
|
|
|
|
|
|
|
|
|
function toCanvasX(x) {
|
|
|
return padding + (x - xMin) * xScale;
|
|
|
}
|
|
|
|
|
|
function toCanvasY(y) {
|
|
|
return canvasHeight - padding - (y - yMin) * yScale;
|
|
|
}
|
|
|
|
|
|
|
|
|
function drawGraph() {
|
|
|
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
|
|
|
|
|
|
|
|
ctx.beginPath();
|
|
|
ctx.strokeStyle = '#64748b';
|
|
|
ctx.lineWidth = 2;
|
|
|
|
|
|
|
|
|
ctx.moveTo(padding, toCanvasY(yMin));
|
|
|
ctx.lineTo(canvasWidth - padding, toCanvasY(yMin));
|
|
|
|
|
|
ctx.moveTo(toCanvasX(xMin), padding);
|
|
|
ctx.lineTo(toCanvasX(xMin), canvasHeight - padding);
|
|
|
ctx.stroke();
|
|
|
|
|
|
|
|
|
ctx.fillStyle = '#475569';
|
|
|
ctx.font = '14px Inter';
|
|
|
ctx.textAlign = 'center';
|
|
|
ctx.textBaseline = 'top';
|
|
|
|
|
|
|
|
|
|
|
|
const xTickStep = 1;
|
|
|
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();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
ctx.fillText('Hours Studied', canvasWidth / 2, canvasHeight - 20);
|
|
|
|
|
|
ctx.textAlign = 'right';
|
|
|
ctx.textBaseline = 'middle';
|
|
|
|
|
|
|
|
|
const yTickStep = (yMax - yMin) / 10 > 20 ? 50 : 20;
|
|
|
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();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
ctx.save();
|
|
|
ctx.translate(20, canvasHeight / 2);
|
|
|
ctx.rotate(-Math.PI / 2);
|
|
|
ctx.textAlign = 'center';
|
|
|
ctx.fillText('Score', 0, 0);
|
|
|
ctx.restore();
|
|
|
|
|
|
|
|
|
|
|
|
ctx.fillStyle = '#3b82f6';
|
|
|
X_data.forEach((x, i) => {
|
|
|
ctx.beginPath();
|
|
|
ctx.arc(toCanvasX(x), toCanvasY(y_data[i]), 5, 0, Math.PI * 2);
|
|
|
ctx.fill();
|
|
|
});
|
|
|
|
|
|
|
|
|
ctx.beginPath();
|
|
|
ctx.strokeStyle = '#ef4444';
|
|
|
ctx.lineWidth = 3;
|
|
|
|
|
|
ctx.moveTo(toCanvasX(xMin), toCanvasY(slope * xMin + intercept));
|
|
|
ctx.lineTo(toCanvasX(xMax), toCanvasY(slope * xMax + intercept));
|
|
|
ctx.stroke();
|
|
|
|
|
|
|
|
|
if (predictedHours !== null && predictedScore !== null) {
|
|
|
const predX = toCanvasX(predictedHours);
|
|
|
const predY = toCanvasY(predictedScore);
|
|
|
|
|
|
|
|
|
ctx.fillStyle = '#22c55e';
|
|
|
ctx.beginPath();
|
|
|
ctx.arc(predX, predY, 6, 0, Math.PI * 2);
|
|
|
ctx.fill();
|
|
|
|
|
|
|
|
|
ctx.strokeStyle = '#22c55e';
|
|
|
ctx.lineWidth = 1.5;
|
|
|
ctx.setLineDash([5, 5]);
|
|
|
|
|
|
|
|
|
ctx.beginPath();
|
|
|
ctx.moveTo(predX, predY);
|
|
|
ctx.lineTo(predX, toCanvasY(yMin));
|
|
|
ctx.stroke();
|
|
|
|
|
|
|
|
|
ctx.beginPath();
|
|
|
ctx.moveTo(predX, predY);
|
|
|
ctx.lineTo(toCanvasX(xMin), predY);
|
|
|
ctx.stroke();
|
|
|
|
|
|
ctx.setLineDash([]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
document.getElementById('predictBtn').addEventListener('click', () => {
|
|
|
|
|
|
const hoursInput = parseFloat(document.getElementById('hoursInput').value);
|
|
|
|
|
|
|
|
|
if (!isNaN(hoursInput)) {
|
|
|
|
|
|
predictedHours = hoursInput;
|
|
|
predictedScore = slope * predictedHours + intercept;
|
|
|
|
|
|
|
|
|
document.getElementById('predictedScore').textContent = predictedScore.toFixed(2);
|
|
|
|
|
|
document.getElementById('predictionOutput').classList.remove('hidden');
|
|
|
|
|
|
|
|
|
setupScaling();
|
|
|
drawGraph();
|
|
|
} else {
|
|
|
|
|
|
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 resizeCanvas() {
|
|
|
|
|
|
const dpi = window.devicePixelRatio;
|
|
|
|
|
|
const rect = canvas.getBoundingClientRect();
|
|
|
|
|
|
|
|
|
canvas.width = rect.width * dpi;
|
|
|
canvas.height = rect.height * dpi;
|
|
|
|
|
|
|
|
|
ctx.scale(dpi, dpi);
|
|
|
|
|
|
|
|
|
setupScaling();
|
|
|
drawGraph();
|
|
|
}
|
|
|
|
|
|
|
|
|
window.addEventListener('load', () => {
|
|
|
resizeCanvas();
|
|
|
|
|
|
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();
|
|
|
}
|
|
|
});
|
|
|
|
|
|
|
|
|
window.addEventListener('resize', resizeCanvas);
|
|
|
|
|
|
|
|
|
canvas.addEventListener('click', (event) => {
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
const clickedHours = xMin + (mouseX - padding) / xScale;
|
|
|
|
|
|
document.getElementById('hoursInput').value = clickedHours.toFixed(1);
|
|
|
|
|
|
document.getElementById('predictBtn').click();
|
|
|
});
|
|
|
|