|
|
| 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();
|
| });
|
|
|