Ver 2.0
This commit is contained in:
@@ -2,13 +2,15 @@ const canvas = document.getElementById('graphCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const width = canvas.width;
|
||||
const height = canvas.height;
|
||||
const center = width / 2; // середина — точка (0,0) (150px)
|
||||
const R_PIXELS = 100; // Длина R в пикселях на канвасе для отрисовки
|
||||
const center = width / 2;
|
||||
|
||||
// Элементы формы
|
||||
const form = document.getElementById('point-form');
|
||||
const yInput = document.getElementById('y-input');
|
||||
const yError = document.getElementById('y-error');
|
||||
const funcInput = document.getElementById('func-input');
|
||||
const funcError = document.getElementById('func-error');
|
||||
const previewFuncBtn = document.getElementById('preview-func-btn');
|
||||
const rHidden = document.getElementById('r-hidden');
|
||||
const rError = document.getElementById('r-error');
|
||||
const rButtons = document.querySelectorAll('.r-button');
|
||||
@@ -22,6 +24,8 @@ const clearHistoryBtn = document.getElementById('clear-history-btn');
|
||||
const STORAGE_KEY = 'web_lab1_history';
|
||||
let history = [];
|
||||
let currentR = null;
|
||||
let formSubmitted = false;
|
||||
let customFunction = null;
|
||||
|
||||
try {
|
||||
const rawData = JSON.parse(localStorage.getItem(STORAGE_KEY));
|
||||
@@ -61,22 +65,71 @@ rButtons.forEach(btn => {
|
||||
btn.classList.add('active');
|
||||
rHidden.value = btn.getAttribute('data-val');
|
||||
currentR = parseFloat(rHidden.value);
|
||||
rError.textContent = '';
|
||||
if (formSubmitted) {
|
||||
rError.textContent = '';
|
||||
}
|
||||
validateForm();
|
||||
drawGraph();
|
||||
});
|
||||
});
|
||||
|
||||
checkboxes.forEach(cb => {
|
||||
cb.addEventListener('change', validateForm);
|
||||
cb.addEventListener('change', () => {
|
||||
if (formSubmitted) {
|
||||
globalError.textContent = '';
|
||||
}
|
||||
validateForm();
|
||||
});
|
||||
});
|
||||
|
||||
yInput.addEventListener('input', () => {
|
||||
validateY();
|
||||
if (formSubmitted) {
|
||||
yError.textContent = '';
|
||||
}
|
||||
validateForm();
|
||||
});
|
||||
|
||||
funcInput.addEventListener('input', () => {
|
||||
if (formSubmitted) {
|
||||
funcError.textContent = '';
|
||||
}
|
||||
validateForm();
|
||||
});
|
||||
|
||||
previewFuncBtn.addEventListener('click', () => {
|
||||
const funcStr = funcInput.value.trim();
|
||||
|
||||
if (funcStr === '') {
|
||||
customFunction = null;
|
||||
funcError.textContent = '';
|
||||
drawGraph();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentR) {
|
||||
funcError.textContent = 'Сначала выберите значение R';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
customFunction = parseFunction(funcStr);
|
||||
funcError.textContent = '';
|
||||
drawGraph();
|
||||
} catch (e) {
|
||||
funcError.textContent = e.message;
|
||||
customFunction = null;
|
||||
}
|
||||
});
|
||||
|
||||
function checkHit(x, y, r) {
|
||||
if (customFunction) {
|
||||
try {
|
||||
return customFunction(x, y, r);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Первая четверть (X >= 0, Y >= 0): Четверть круга (R)
|
||||
if (x >= 0 && y >= 0) {
|
||||
return (x * x + y * y) <= (r * r);
|
||||
@@ -97,42 +150,95 @@ function checkHit(x, y, r) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function validateY() {
|
||||
function parseFunction(funcStr) {
|
||||
if (!funcStr || funcStr.trim() === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalized = funcStr.trim()
|
||||
.replace(/abs\(/g, 'Math.abs(')
|
||||
.replace(/sin\(/g, 'Math.sin(')
|
||||
.replace(/cos\(/g, 'Math.cos(')
|
||||
.replace(/tan\(/g, 'Math.tan(')
|
||||
.replace(/sqrt\(/g, 'Math.sqrt(')
|
||||
.replace(/\^/g, '**');
|
||||
|
||||
const allowedChars = /^[xyrRMathabsincoqrt.()+\-*/<>=\s\d.]+$/;
|
||||
if (!allowedChars.test(normalized)) {
|
||||
throw new Error('Недопустимые символы в функции');
|
||||
}
|
||||
|
||||
try {
|
||||
const func = new Function('x', 'y', 'r', 'R', `return ${normalized};`);
|
||||
func(0, 0, 1, 1);
|
||||
return (x, y, r) => func(x, y, r, r);
|
||||
} catch (e) {
|
||||
throw new Error('Синтаксическая ошибка в функции');
|
||||
}
|
||||
}
|
||||
|
||||
function validateFunction(showErrors = false) {
|
||||
const funcStr = funcInput.value.trim();
|
||||
if (funcStr === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
parseFunction(funcStr);
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (showErrors) {
|
||||
funcError.textContent = e.message;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function validateY(showErrors = false) {
|
||||
const valStr = yInput.value.trim().replace(',', '.');
|
||||
if (valStr === '') {
|
||||
yError.textContent = 'Поле Y обязательно к заполнению.';
|
||||
if (showErrors) {
|
||||
yError.textContent = 'Поле Y обязательно к заполнению.';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const y = Number(valStr);
|
||||
if (isNaN(y)) {
|
||||
yError.textContent = 'Y должно быть числом (буквы недопустимы).';
|
||||
if (showErrors) {
|
||||
yError.textContent = 'Y должно быть числом (буквы недопустимы).';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((y < -3 || y > 5) && y !== 81) {
|
||||
yError.textContent = 'Значение Y должно принадлежать диапазону от -3 до 5 включительно.';
|
||||
if (showErrors) {
|
||||
yError.textContent = 'Значение Y должно принадлежать диапазону от -3 до 5 включительно.';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
yError.textContent = '';
|
||||
return true;
|
||||
}
|
||||
|
||||
function validateForm() {
|
||||
function validateForm(showErrors = false) {
|
||||
let isValid = true;
|
||||
|
||||
if (!validateY()) isValid = false;
|
||||
if (!validateY(showErrors)) isValid = false;
|
||||
|
||||
if (!validateFunction(showErrors)) isValid = false;
|
||||
|
||||
const validRValues = [1, 2, 3, 4, 5];
|
||||
if (!rHidden.value) {
|
||||
rError.textContent = 'Необходимо выбрать значение R.';
|
||||
if (showErrors) {
|
||||
rError.textContent = 'Необходимо выбрать значение R.';
|
||||
}
|
||||
isValid = false;
|
||||
} else if (!validRValues.includes(parseFloat(rHidden.value))) {
|
||||
rError.textContent = 'Обнаружена подмена значения R!';
|
||||
if (showErrors) {
|
||||
rError.textContent = 'Обнаружена подмена значения R!';
|
||||
}
|
||||
isValid = false;
|
||||
} else {
|
||||
rError.textContent = '';
|
||||
}
|
||||
|
||||
const validXValues = [-2, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, 2];
|
||||
@@ -149,13 +255,15 @@ function validateForm() {
|
||||
});
|
||||
|
||||
if (!xChecked) {
|
||||
globalError.textContent = 'Выберите хотя бы одно значение X.';
|
||||
if (showErrors) {
|
||||
globalError.textContent = 'Выберите хотя бы одно значение X.';
|
||||
}
|
||||
isValid = false;
|
||||
} else if (xSpoofed) {
|
||||
globalError.textContent = 'Ошибка: передано недопустимое значение X!';
|
||||
if (showErrors) {
|
||||
globalError.textContent = 'Ошибка: передано недопустимое значение X!';
|
||||
}
|
||||
isValid = false;
|
||||
} else {
|
||||
globalError.textContent = '';
|
||||
}
|
||||
|
||||
submitBtn.disabled = !isValid;
|
||||
@@ -165,7 +273,9 @@ function validateForm() {
|
||||
form.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!validateForm()) return;
|
||||
formSubmitted = true;
|
||||
|
||||
if (!validateForm(true)) return;
|
||||
|
||||
const y = parseFloat(yInput.value.trim().replace(',', '.'));
|
||||
// пасхалка
|
||||
@@ -181,6 +291,14 @@ form.addEventListener('submit', (e) => {
|
||||
}
|
||||
|
||||
const r = parseFloat(rHidden.value);
|
||||
|
||||
const funcStr = funcInput.value.trim();
|
||||
try {
|
||||
customFunction = parseFunction(funcStr);
|
||||
} catch (e) {
|
||||
funcError.textContent = e.message;
|
||||
return;
|
||||
}
|
||||
|
||||
// Дата, время и тд
|
||||
const dateString = new Date().toLocaleString("ru-RU", {
|
||||
@@ -233,25 +351,57 @@ function renderHistory() {
|
||||
|
||||
function drawGraph() {
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
ctx.fillStyle = '#3399FF';
|
||||
|
||||
const R_PIXELS = currentR ? (100 * currentR / 3) : 100;
|
||||
|
||||
if (customFunction && currentR) {
|
||||
const imageData = ctx.createImageData(width, height);
|
||||
const data = imageData.data;
|
||||
|
||||
for (let py = 0; py < height; py++) {
|
||||
for (let px = 0; px < width; px++) {
|
||||
const x = ((px - center) / R_PIXELS) * currentR;
|
||||
const y = ((center - py) / R_PIXELS) * currentR;
|
||||
|
||||
const hit = checkHit(x, y, currentR);
|
||||
|
||||
const index = (py * width + px) * 4;
|
||||
if (hit) {
|
||||
data[index] = 51;
|
||||
data[index + 1] = 153;
|
||||
data[index + 2] = 255;
|
||||
data[index + 3] = 255;
|
||||
} else {
|
||||
data[index] = 255;
|
||||
data[index + 1] = 255;
|
||||
data[index + 2] = 255;
|
||||
data[index + 3] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
} else {
|
||||
ctx.fillStyle = '#3399FF';
|
||||
|
||||
// Прямоугольник
|
||||
ctx.fillRect(center - R_PIXELS, center - R_PIXELS / 2, R_PIXELS, R_PIXELS / 2);
|
||||
// Прямоугольник
|
||||
ctx.fillRect(center - R_PIXELS, center - R_PIXELS / 2, R_PIXELS, R_PIXELS / 2);
|
||||
|
||||
// Четверть круга
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(center, center);
|
||||
ctx.arc(center, center, R_PIXELS, -Math.PI / 2, 0, false);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
// Четверть круга
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(center, center);
|
||||
ctx.arc(center, center, R_PIXELS, -Math.PI / 2, 0, false);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
// Треугольник
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(center, center);
|
||||
ctx.lineTo(center + R_PIXELS, center);
|
||||
ctx.lineTo(center, center + R_PIXELS / 2);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
// Треугольник
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(center, center);
|
||||
ctx.lineTo(center + R_PIXELS, center);
|
||||
ctx.lineTo(center, center + R_PIXELS / 2);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// Оси
|
||||
ctx.strokeStyle = '#000';
|
||||
@@ -306,8 +456,8 @@ function drawGraph() {
|
||||
|
||||
if (currentR && currentR > 0) {
|
||||
history.forEach(rec => {
|
||||
let drawX = center + (rec.x / currentR) * R_PIXELS;
|
||||
let drawY = center - (rec.y / currentR) * R_PIXELS;
|
||||
let drawX = center + (rec.x / rec.r) * R_PIXELS;
|
||||
let drawY = center - (rec.y / rec.r) * R_PIXELS;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(drawX, drawY, 4, 0, 2 * Math.PI, false);
|
||||
|
||||
Reference in New Issue
Block a user