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