2022-11-24 05:04:14 +07:00
|
|
|
|
import Victor from "https://cdn.skypack.dev/victor@1.1.0";
|
2022-11-22 05:40:30 +07:00
|
|
|
|
|
2022-11-26 04:38:23 +07:00
|
|
|
|
'use strict';
|
2022-11-01 06:19:17 +07:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
|
|
|
|
*/
|
|
|
|
|
class graph {
|
|
|
|
|
// Оболочка (instanceof HTMLElement)
|
2022-11-22 05:40:30 +07:00
|
|
|
|
#shell = document.getElementById("graph");
|
|
|
|
|
get shell() {
|
|
|
|
|
return this.#shell;
|
|
|
|
|
}
|
2022-11-01 06:19:17 +07:00
|
|
|
|
|
|
|
|
|
// Реестр узлов
|
2022-11-22 06:43:57 +07:00
|
|
|
|
#nodes = new Set();
|
|
|
|
|
get nodes() {
|
|
|
|
|
return this.#nodes;
|
|
|
|
|
}
|
2022-11-01 06:19:17 +07:00
|
|
|
|
|
|
|
|
|
// Реестр соединений
|
2022-11-22 06:43:57 +07:00
|
|
|
|
#connections = new Set();
|
|
|
|
|
get connections() {
|
|
|
|
|
return this.#connections;
|
|
|
|
|
}
|
2022-11-01 06:19:17 +07:00
|
|
|
|
|
|
|
|
|
// Класс узла
|
2022-11-22 06:43:57 +07:00
|
|
|
|
#node = class node {
|
2022-11-01 06:19:17 +07:00
|
|
|
|
// Реестр входящих соединений
|
2022-11-22 06:43:57 +07:00
|
|
|
|
#inputs = new Set();
|
2022-11-22 05:40:30 +07:00
|
|
|
|
get inputs() {
|
|
|
|
|
return this.#inputs;
|
|
|
|
|
}
|
2022-11-01 06:19:17 +07:00
|
|
|
|
|
|
|
|
|
// Реестр исходящих соединений
|
2022-11-22 06:43:57 +07:00
|
|
|
|
#outputs = new Set();
|
2022-11-22 05:40:30 +07:00
|
|
|
|
get outputs() {
|
|
|
|
|
return this.#outputs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Оператор
|
|
|
|
|
#operator;
|
|
|
|
|
get operator() {
|
|
|
|
|
return this.#operator;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// HTML-элемент
|
|
|
|
|
#element;
|
|
|
|
|
get element() {
|
|
|
|
|
return this.#element;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Наблюдатель
|
|
|
|
|
#observer = null;
|
|
|
|
|
get observer() {
|
|
|
|
|
return this.#observer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Реестр запрещённых к изменению параметров
|
|
|
|
|
#block = new Set(["events"]);
|
|
|
|
|
get block() {
|
|
|
|
|
return this.#block;
|
|
|
|
|
}
|
2022-11-01 06:19:17 +07:00
|
|
|
|
|
2022-11-22 18:31:26 +07:00
|
|
|
|
// Диаметр узла
|
2022-11-01 06:19:17 +07:00
|
|
|
|
#diameter = 100;
|
2022-11-22 05:40:30 +07:00
|
|
|
|
get diameter() {
|
|
|
|
|
return this.#diameter;
|
|
|
|
|
}
|
2022-11-22 18:31:26 +07:00
|
|
|
|
|
|
|
|
|
// Степень увеличения диаметра
|
|
|
|
|
#increase = 0;
|
2022-11-22 05:40:30 +07:00
|
|
|
|
get increase() {
|
|
|
|
|
return this.#increase;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-22 18:31:26 +07:00
|
|
|
|
// Величина степени увеличения диаметра
|
|
|
|
|
#addition = 12;
|
|
|
|
|
get addition() {
|
|
|
|
|
return this.#addition;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-23 23:50:13 +07:00
|
|
|
|
// Счётчик итераций
|
|
|
|
|
iteration = 0;
|
|
|
|
|
|
|
|
|
|
// Ограничение максимального количества итераций за вызов
|
|
|
|
|
limit = 3000;
|
|
|
|
|
|
2022-11-22 18:31:26 +07:00
|
|
|
|
// Обработка событий
|
|
|
|
|
actions = {
|
|
|
|
|
collision: true,
|
2022-11-23 23:50:13 +07:00
|
|
|
|
pushing: true,
|
|
|
|
|
pulling: true
|
2022-11-22 18:31:26 +07:00
|
|
|
|
};
|
|
|
|
|
|
2022-11-22 06:43:57 +07:00
|
|
|
|
constructor(operator, data) {
|
2022-11-22 18:31:26 +07:00
|
|
|
|
// Инициализация HTML-элемента узла
|
2022-11-24 14:24:23 +07:00
|
|
|
|
const article = document.createElement("article");
|
|
|
|
|
article.id = operator.nodes.size;
|
2022-11-26 04:38:23 +07:00
|
|
|
|
article.classList.add(data.color ?? 'white', "node", "unselectable");
|
2022-11-24 14:24:23 +07:00
|
|
|
|
if (typeof data.href === "string") {
|
|
|
|
|
article.href = data.href;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-26 04:38:23 +07:00
|
|
|
|
// Инициализация заголовка
|
|
|
|
|
const title = document.createElement("h4");
|
|
|
|
|
title.classList.add('title');
|
2022-11-26 05:04:45 +07:00
|
|
|
|
title.innerText = data.title ?? '';
|
2022-11-01 06:19:17 +07:00
|
|
|
|
|
2022-11-24 14:24:23 +07:00
|
|
|
|
// Запись в оболочку
|
|
|
|
|
article.appendChild(title);
|
|
|
|
|
|
2022-11-26 04:38:23 +07:00
|
|
|
|
// Инициализация описания
|
|
|
|
|
const description = document.createElement("div");
|
|
|
|
|
description.classList.add('description');
|
2022-11-26 05:04:45 +07:00
|
|
|
|
if (typeof data.popup === 'string') description.title = data.popup;
|
2022-11-01 06:19:17 +07:00
|
|
|
|
|
2022-11-26 04:38:23 +07:00
|
|
|
|
// Запись блокировки открытия описания в случае, если был перемещён узел
|
|
|
|
|
title.onmousedown = (onmousedown) => {
|
2022-11-26 06:18:53 +07:00
|
|
|
|
// Инициализация координат
|
|
|
|
|
let x = onmousedown.pageX;
|
|
|
|
|
let y = onmousedown.pageY;
|
|
|
|
|
|
|
|
|
|
// Запись события открытия описания
|
|
|
|
|
title.onclick = (onclick) => {
|
|
|
|
|
// Отображение описания
|
|
|
|
|
show();
|
|
|
|
|
|
|
|
|
|
// Удаление событий
|
|
|
|
|
title.onclick = title.onmousemove = null;
|
|
|
|
|
|
|
|
|
|
// Реинициализация координат
|
|
|
|
|
x = onclick.pageX;
|
|
|
|
|
y = onclick.pageY;
|
|
|
|
|
|
|
|
|
|
// Удаление иконки курсора
|
|
|
|
|
title.style.cursor = null;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2022-11-26 04:38:23 +07:00
|
|
|
|
|
|
|
|
|
title.onmousemove = (onmousemove) => {
|
|
|
|
|
// Курсор сдвинут более чем на 15 пикселей?
|
2022-11-26 06:18:53 +07:00
|
|
|
|
if (Math.abs(x - onmousemove.pageX) > 15 || Math.abs(y - onmousemove.pageY) > 15) {
|
2022-11-26 04:38:23 +07:00
|
|
|
|
// Запись иконки курсора
|
|
|
|
|
title.style.cursor = 'grabbing';
|
|
|
|
|
|
2022-11-26 06:18:53 +07:00
|
|
|
|
// Запись события для переноса узла
|
|
|
|
|
title.onclick = (onclick) => {
|
2022-11-26 04:38:23 +07:00
|
|
|
|
// Удаление событий
|
2022-11-26 06:18:53 +07:00
|
|
|
|
title.onclick = title.onmousemove = null;
|
|
|
|
|
|
|
|
|
|
// Реинициализация координат
|
|
|
|
|
x = onclick.pageX;
|
|
|
|
|
y = onclick.pageY;
|
2022-11-26 04:38:23 +07:00
|
|
|
|
|
|
|
|
|
// Удаление иконки курсора
|
|
|
|
|
title.style.cursor = null;
|
2022-11-26 06:18:53 +07:00
|
|
|
|
|
|
|
|
|
return false;
|
2022-11-26 04:38:23 +07:00
|
|
|
|
}
|
2022-11-26 06:18:53 +07:00
|
|
|
|
} else {
|
|
|
|
|
// Запись события открытия описания
|
|
|
|
|
title.onclick = (onclick) => {
|
|
|
|
|
// Отображение описания
|
|
|
|
|
show();
|
2022-11-26 04:38:23 +07:00
|
|
|
|
|
2022-11-26 06:18:53 +07:00
|
|
|
|
// Удаление событий
|
|
|
|
|
title.onclick = title.onmousemove = null;
|
|
|
|
|
|
|
|
|
|
// Реинициализация координат
|
|
|
|
|
x = onclick.pageX;
|
|
|
|
|
y = onclick.pageY;
|
|
|
|
|
|
|
|
|
|
// Удаление иконки курсора
|
|
|
|
|
title.style.cursor = null;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
}
|
2022-11-26 04:38:23 +07:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Запись в оболочку
|
|
|
|
|
article.appendChild(description);
|
|
|
|
|
|
|
|
|
|
// Инициализация левой фигуры для обёртки текста
|
|
|
|
|
const left = document.createElement("span");
|
|
|
|
|
left.classList.add('left', 'wrapper');
|
|
|
|
|
|
|
|
|
|
// Запись в описание
|
|
|
|
|
description.appendChild(left);
|
|
|
|
|
|
|
|
|
|
// Инициализация правой фигуры для обёртки текста
|
|
|
|
|
const right = document.createElement("span");
|
|
|
|
|
right.classList.add('right', 'wrapper');
|
|
|
|
|
|
|
|
|
|
// Запись в описание
|
|
|
|
|
description.appendChild(right);
|
|
|
|
|
|
|
|
|
|
// Инициализация ссылки на источник
|
|
|
|
|
const a = document.createElement("a");
|
2022-11-26 05:04:45 +07:00
|
|
|
|
if (typeof data.link === 'object' && typeof data.link.name === 'string') a.innerText = data.link.name;
|
|
|
|
|
if (typeof data.link === 'object' && typeof data.link.href === 'string') a.href = data.link.href;
|
|
|
|
|
if (typeof data.link === 'object' && typeof data.link.class === 'object') a.classList.add(...data.link.class);
|
|
|
|
|
if (typeof data.link === 'object' && typeof data.link.title === 'string') a.title = data.link.title;
|
2022-11-26 04:38:23 +07:00
|
|
|
|
|
|
|
|
|
// Блокировка событий браузера (чтобы не мешать переноса узла)
|
|
|
|
|
a.ondragstart = a.onselectstart = fn => { return false };
|
|
|
|
|
|
|
|
|
|
// Запись блокировки перехода по ссылке в случае, если был перемещён узел
|
|
|
|
|
a.onmousedown = (onmousedown) => {
|
2022-11-26 06:18:53 +07:00
|
|
|
|
// Инициализация координат
|
|
|
|
|
let x = onmousedown.pageX;
|
|
|
|
|
let y = onmousedown.pageY;
|
|
|
|
|
|
|
|
|
|
// Запись события открытия описания
|
|
|
|
|
a.onclick = (onclick) => {
|
|
|
|
|
// Удаление событий
|
|
|
|
|
a.onclick = a.onmousemove = null;
|
|
|
|
|
|
|
|
|
|
// Реинициализация координат
|
|
|
|
|
x = onclick.pageX;
|
|
|
|
|
y = onclick.pageY;
|
|
|
|
|
|
|
|
|
|
// Удаление иконки курсора
|
|
|
|
|
a.style.cursor = null;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-26 04:38:23 +07:00
|
|
|
|
a.onmousemove = (onmousemove) => {
|
|
|
|
|
// Курсор сдвинут более чем на 15 пикселей?
|
2022-11-26 06:18:53 +07:00
|
|
|
|
if (Math.abs(x - onmousemove.pageX) > 15 || Math.abs(y - onmousemove.pageY) > 15) {
|
2022-11-26 04:38:23 +07:00
|
|
|
|
// Запись иконки курсора
|
|
|
|
|
a.style.cursor = 'grabbing';
|
|
|
|
|
|
2022-11-26 06:18:53 +07:00
|
|
|
|
// Запись события для переноса узла
|
|
|
|
|
a.onclick = (onclick) => {
|
2022-11-26 04:38:23 +07:00
|
|
|
|
// Удаление событий
|
2022-11-26 06:18:53 +07:00
|
|
|
|
a.onclick = a.onmousemove = null;
|
|
|
|
|
|
|
|
|
|
// Реинициализация координат
|
|
|
|
|
x = onclick.pageX;
|
|
|
|
|
y = onclick.pageY;
|
2022-11-26 04:38:23 +07:00
|
|
|
|
|
|
|
|
|
// Удаление иконки курсора
|
|
|
|
|
a.style.cursor = null;
|
2022-11-26 06:18:53 +07:00
|
|
|
|
|
|
|
|
|
return false;
|
2022-11-26 04:38:23 +07:00
|
|
|
|
}
|
2022-11-26 06:18:53 +07:00
|
|
|
|
} else {
|
|
|
|
|
// Запись события открытия описания
|
|
|
|
|
a.onclick = (onclick) => {
|
|
|
|
|
// Удаление событий
|
|
|
|
|
a.onclick = a.onmousemove = null;
|
|
|
|
|
|
|
|
|
|
// Реинициализация координат
|
|
|
|
|
x = onclick.pageX;
|
|
|
|
|
y = onclick.pageY;
|
2022-11-26 04:38:23 +07:00
|
|
|
|
|
2022-11-26 06:18:53 +07:00
|
|
|
|
// Удаление иконки курсора
|
|
|
|
|
a.style.cursor = null;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
}
|
2022-11-26 04:38:23 +07:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Запись в описание
|
|
|
|
|
description.appendChild(a);
|
|
|
|
|
|
|
|
|
|
// Запись текста в описание
|
|
|
|
|
const text = document.createElement("p");
|
2022-11-26 05:04:45 +07:00
|
|
|
|
if (typeof data.description === 'string') text.innerText = data.description;
|
2022-11-26 04:38:23 +07:00
|
|
|
|
|
|
|
|
|
// Запись в оболочку
|
|
|
|
|
description.appendChild(text);
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
typeof data.cover === "string"
|
|
|
|
|
) {
|
2022-11-26 05:04:45 +07:00
|
|
|
|
// Получено изображение-обложка
|
2022-11-26 04:38:23 +07:00
|
|
|
|
|
2022-11-26 05:04:45 +07:00
|
|
|
|
// Инициализация изображения-обложки
|
2022-11-26 04:38:23 +07:00
|
|
|
|
const cover = document.createElement("img");
|
2022-11-26 05:04:45 +07:00
|
|
|
|
if (typeof cover.src === 'string') cover.src = data.cover;
|
|
|
|
|
if (typeof cover.alt === 'string') cover.alt = data.title;
|
2022-11-26 04:38:23 +07:00
|
|
|
|
cover.classList.add('cover', 'unselectable');
|
|
|
|
|
|
|
|
|
|
// Запись в описание
|
|
|
|
|
description.appendChild(cover);
|
2022-11-24 14:24:23 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
typeof data.append === "HTMLCollection" ||
|
|
|
|
|
typeof data.append === "HTMLElement"
|
|
|
|
|
) {
|
2022-11-26 04:38:23 +07:00
|
|
|
|
// Получены другие HTML-элементы
|
2022-11-01 06:19:17 +07:00
|
|
|
|
|
|
|
|
|
// Запись в оболочку
|
2022-11-24 14:24:23 +07:00
|
|
|
|
article.appendChild(data.append);
|
2022-11-01 06:19:17 +07:00
|
|
|
|
}
|
|
|
|
|
|
2022-11-26 05:50:15 +07:00
|
|
|
|
// Инициализация кнопки закрытия
|
|
|
|
|
const close = document.createElement('i');
|
|
|
|
|
close.classList.add('icon', 'close');
|
|
|
|
|
close.style.display = 'none';
|
|
|
|
|
close.title = 'Закрыть';
|
|
|
|
|
|
|
|
|
|
// Инициализация события закрытия окна описания
|
|
|
|
|
close.onclick = fn => { hide() };
|
|
|
|
|
|
|
|
|
|
// Запись в оболочку
|
|
|
|
|
article.appendChild(close);
|
|
|
|
|
|
2022-11-22 18:31:26 +07:00
|
|
|
|
// Запись в документ
|
2022-11-24 14:24:23 +07:00
|
|
|
|
operator.shell.appendChild(article);
|
2022-11-22 18:31:26 +07:00
|
|
|
|
|
2022-11-26 04:38:23 +07:00
|
|
|
|
// Запись диаметра описания в зависимости от размера заголовка (чтобы вмещался)
|
|
|
|
|
description.style.width = description.style.height = (a.offsetWidth === 0 ? 50 : a.offsetWidth) * 3 + 'px';
|
|
|
|
|
|
|
|
|
|
// Запись отступа заголовка (чтобы был по центру описания)
|
|
|
|
|
a.style.left = description.offsetWidth / 2 - a.offsetWidth / 2 + 'px';
|
|
|
|
|
|
|
|
|
|
// Сокрытие описания (выполняется после расчёта размера потому, что иначе размер будет недоступен)
|
|
|
|
|
description.style.display = "none";
|
|
|
|
|
|
2022-11-26 05:50:15 +07:00
|
|
|
|
/**
|
|
|
|
|
* Показать описание
|
|
|
|
|
*/
|
|
|
|
|
function show() {
|
|
|
|
|
// Отображение описания и кнопки закрытия описания
|
|
|
|
|
description.style.display = close.style.display = null;
|
|
|
|
|
|
|
|
|
|
// Сдвиг кнопки закрытия описания
|
|
|
|
|
close.style.top = close.style.right = -(((description.offsetWidth - article.offsetWidth) / 4) + description.offsetWidth / 8) + 'px';
|
|
|
|
|
|
|
|
|
|
// Размер кнопки закрытия описания
|
|
|
|
|
close.style.scale = 1.3;
|
|
|
|
|
|
|
|
|
|
// Прозрачность кнопки закрытия описания (плавное появление)
|
|
|
|
|
close.style.opacity = 1;
|
|
|
|
|
|
|
|
|
|
// Расположение выше остальных узлов
|
|
|
|
|
article.style.zIndex = close.style.zIndex = 1000;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Скрыть описание
|
|
|
|
|
*/
|
|
|
|
|
function hide() {
|
|
|
|
|
// Скрытие описания и кнопки закрытия описания
|
|
|
|
|
description.style.display = close.style.display = 'none';
|
|
|
|
|
|
|
|
|
|
// Удаление всех изменённых аттрибутов
|
|
|
|
|
close.style.top = close.style.right = article.style.zIndex = close.style.zIndex = close.style.scale = close.style.opacity = null;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-01 06:19:17 +07:00
|
|
|
|
// Запись в свойство
|
2022-11-24 14:24:23 +07:00
|
|
|
|
this.#element = article;
|
2022-11-22 05:40:30 +07:00
|
|
|
|
|
|
|
|
|
// Запись в свойство
|
2022-11-22 06:43:57 +07:00
|
|
|
|
this.#operator = operator;
|
2022-11-01 06:19:17 +07:00
|
|
|
|
|
|
|
|
|
// Инициализация
|
|
|
|
|
this.init();
|
2022-11-22 18:31:26 +07:00
|
|
|
|
|
|
|
|
|
// Перемещение
|
|
|
|
|
this.move(
|
|
|
|
|
operator.shell.offsetWidth / 2 -
|
2022-11-26 04:38:23 +07:00
|
|
|
|
this.#diameter / 2 +
|
|
|
|
|
(0.5 - Math.random()) * 500,
|
2022-11-22 18:31:26 +07:00
|
|
|
|
operator.shell.offsetHeight / 2 -
|
2022-11-26 04:38:23 +07:00
|
|
|
|
this.#diameter / 2 +
|
|
|
|
|
(0.5 - Math.random()) * 500,
|
2022-11-22 18:31:26 +07:00
|
|
|
|
true,
|
2022-11-23 23:50:13 +07:00
|
|
|
|
true,
|
2022-11-22 18:31:26 +07:00
|
|
|
|
true
|
|
|
|
|
);
|
2022-11-01 06:19:17 +07:00
|
|
|
|
}
|
|
|
|
|
|
2022-11-22 05:40:30 +07:00
|
|
|
|
init(increase = 0) {
|
2022-11-22 18:31:26 +07:00
|
|
|
|
// Запись в свойство
|
|
|
|
|
this.#increase = increase;
|
2022-11-22 05:40:30 +07:00
|
|
|
|
|
2022-11-22 18:31:26 +07:00
|
|
|
|
// Инициализация диаметра
|
|
|
|
|
if (this.#increase !== 0)
|
|
|
|
|
this.#diameter += this.#addition ** this.#increase;
|
|
|
|
|
|
|
|
|
|
// Инициализация размера HTML-элемента
|
2022-11-01 06:19:17 +07:00
|
|
|
|
this.element.style.width = this.element.style.height =
|
2022-11-22 05:40:30 +07:00
|
|
|
|
this.#diameter + "px";
|
|
|
|
|
|
2022-11-26 04:38:23 +07:00
|
|
|
|
// Инициализация описания
|
|
|
|
|
const description = this.element.getElementsByClassName('description')[0];
|
|
|
|
|
|
|
|
|
|
// Запись отступа описания (чтобы был по центру узла)
|
|
|
|
|
description.style.display = null;
|
|
|
|
|
description.style.marginLeft = description.style.marginTop = (this.element.offsetWidth - description.offsetWidth) / 2 + 'px';
|
|
|
|
|
description.style.display = 'none';
|
|
|
|
|
|
2022-11-22 05:40:30 +07:00
|
|
|
|
// Инициализация ссылки на ядро
|
|
|
|
|
const _this = this;
|
|
|
|
|
|
|
|
|
|
// Инициализация наблюдателя
|
|
|
|
|
this.#observer = new MutationObserver(function (mutations) {
|
|
|
|
|
for (const mutation of mutations) {
|
|
|
|
|
if (mutation.type === "attributes") {
|
2022-11-22 18:31:26 +07:00
|
|
|
|
// Перехвачено изменение аттрибута
|
|
|
|
|
|
2022-11-22 05:40:30 +07:00
|
|
|
|
// Запись параметра в инстанцию бегущей строки
|
|
|
|
|
_this.configure(mutation.attributeName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Активация наблюдения
|
|
|
|
|
this.observer.observe(this.element, {
|
|
|
|
|
attributes: true,
|
|
|
|
|
attributeOldValue: true
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-23 23:50:13 +07:00
|
|
|
|
move(x, y, collision = false, pushing = false, pulling = false) {
|
2022-11-22 05:40:30 +07:00
|
|
|
|
// Запись отступов
|
|
|
|
|
this.element.style.left = x + "px";
|
|
|
|
|
this.element.style.top = y + "px";
|
|
|
|
|
|
|
|
|
|
// Запись аттрибутов с координатами
|
|
|
|
|
this.element.setAttribute("data-graph-x", x);
|
|
|
|
|
this.element.setAttribute("data-graph-y", y);
|
|
|
|
|
|
2022-11-23 23:50:13 +07:00
|
|
|
|
// Инициализация реестров узлов
|
|
|
|
|
if (collision === true) collision = new Set();
|
|
|
|
|
if (pushing === true) pushing = new Set();
|
|
|
|
|
if (pulling === true) pulling = new Set();
|
|
|
|
|
|
|
|
|
|
// Обработка столкновений
|
|
|
|
|
if (collision && !collision.has(this))
|
|
|
|
|
this.collision(this.operator.nodes, collision);
|
|
|
|
|
|
|
|
|
|
// Инициализация буфера реестра узлов
|
|
|
|
|
const registry = new Set(this.operator.nodes);
|
|
|
|
|
|
|
|
|
|
if (pushing && !pushing.has(this)) {
|
|
|
|
|
// Активно отталкивание
|
|
|
|
|
|
|
|
|
|
// Инициализация счётчика циклов
|
|
|
|
|
let iterations = 50;
|
|
|
|
|
|
|
|
|
|
for (const connection of this.inputs) {
|
|
|
|
|
// Перебор входящих соединений
|
|
|
|
|
|
|
|
|
|
// Ограничение выполнения
|
|
|
|
|
if (--iterations <= 0) break;
|
|
|
|
|
|
|
|
|
|
// Защита от повторной обработки
|
|
|
|
|
if (pushing.has(connection.from)) continue;
|
|
|
|
|
|
|
|
|
|
// Удаление из буфера реестра узлов
|
|
|
|
|
registry.delete(connection.from);
|
|
|
|
|
|
|
|
|
|
// Обработка отталкивания
|
|
|
|
|
this.pushing(new Set([connection.from]), pushing);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Реинициализация счётчика циклов
|
|
|
|
|
iterations = 50;
|
|
|
|
|
|
|
|
|
|
for (const connection of this.outputs) {
|
|
|
|
|
// Перебор исходящих соединений
|
|
|
|
|
|
|
|
|
|
// Ограничение выполнения
|
|
|
|
|
if (--iterations <= 0) break;
|
|
|
|
|
|
|
|
|
|
// Защита от повторной обработки
|
|
|
|
|
if (pushing.has(connection.to)) continue;
|
|
|
|
|
|
|
|
|
|
// Удаление из буфера реестра узлов
|
|
|
|
|
registry.delete(connection.to);
|
|
|
|
|
|
|
|
|
|
// Обработка отталкивания
|
|
|
|
|
this.pushing(new Set([connection.to]), pushing);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pulling && !pulling.has(this)) {
|
|
|
|
|
// Активно притягивание
|
|
|
|
|
|
|
|
|
|
// Инициализация счётчика циклов
|
|
|
|
|
let iterations = 50;
|
|
|
|
|
|
|
|
|
|
for (const connection of this.inputs) {
|
|
|
|
|
// Перебор входящих соединений
|
|
|
|
|
|
|
|
|
|
// Ограничение выполнения
|
|
|
|
|
if (--iterations <= 0) break;
|
|
|
|
|
|
|
|
|
|
// Защита от повторной обработки
|
|
|
|
|
if (pulling.has(connection.from)) continue;
|
|
|
|
|
|
|
|
|
|
// Удаление из буфера реестра узлов
|
|
|
|
|
registry.delete(connection.from);
|
|
|
|
|
|
|
|
|
|
// Обработка притягивания
|
|
|
|
|
this.pulling(new Set([connection.from]), pulling);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Реинициализация счётчика циклов
|
|
|
|
|
iterations = 50;
|
|
|
|
|
|
|
|
|
|
for (const connection of this.outputs) {
|
|
|
|
|
// Перебор входящих соединений
|
|
|
|
|
|
|
|
|
|
// Ограничение выполнения
|
|
|
|
|
if (--iterations <= 0) break;
|
|
|
|
|
|
|
|
|
|
// Защита от повторной обработки
|
|
|
|
|
if (pulling.has(connection.to)) continue;
|
|
|
|
|
|
|
|
|
|
// Удаление из буфера реестра узлов
|
|
|
|
|
registry.delete(connection.to);
|
|
|
|
|
|
|
|
|
|
// Обработка притягивания
|
|
|
|
|
this.pulling(new Set([connection.to]), pulling);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Обработка отталкивания остальных узлов
|
|
|
|
|
if (pushing) this.pushing(registry, pushing);
|
|
|
|
|
|
|
|
|
|
// Синхронизация местоположения исходящих соединений
|
2022-11-22 18:31:26 +07:00
|
|
|
|
for (const connection of this.outputs) connection.sync(this);
|
2022-11-22 05:40:30 +07:00
|
|
|
|
|
2022-11-22 18:31:26 +07:00
|
|
|
|
// Синхронизация местоположения входящих соединений
|
|
|
|
|
for (const connection of this.inputs) connection.sync(this);
|
2022-11-22 05:40:30 +07:00
|
|
|
|
}
|
|
|
|
|
|
2022-11-23 23:50:13 +07:00
|
|
|
|
collision(nodes, involved) {
|
2022-11-22 05:40:30 +07:00
|
|
|
|
// Инициализация буфера реестра узлов
|
2022-11-22 18:31:26 +07:00
|
|
|
|
const registry = new Set(nodes);
|
2022-11-22 05:40:30 +07:00
|
|
|
|
|
|
|
|
|
// Удаление текущего узла из буфера
|
2022-11-22 18:31:26 +07:00
|
|
|
|
registry.delete(this);
|
2022-11-22 05:40:30 +07:00
|
|
|
|
|
2022-11-23 23:50:13 +07:00
|
|
|
|
// Обработка столкновения с узлами
|
2022-11-22 18:31:26 +07:00
|
|
|
|
for (const node of registry) {
|
2022-11-22 05:40:30 +07:00
|
|
|
|
// Перебор узлов в реестре
|
|
|
|
|
|
2022-11-23 23:50:13 +07:00
|
|
|
|
// Защита от повторной обработки узла
|
|
|
|
|
if (involved.has(node)) continue;
|
|
|
|
|
|
2022-11-22 05:40:30 +07:00
|
|
|
|
// Инициализация вектора между узлами
|
|
|
|
|
let between;
|
|
|
|
|
|
|
|
|
|
// Инициализация ускорения
|
|
|
|
|
let increase = 0;
|
|
|
|
|
|
2022-11-22 18:31:26 +07:00
|
|
|
|
// Инициализация максимального количества итераций
|
2022-11-23 23:50:13 +07:00
|
|
|
|
let iterations = 30;
|
2022-11-22 05:40:30 +07:00
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
// Произошла коллизия (границы кругов перекрылись)
|
|
|
|
|
|
2022-11-23 23:50:13 +07:00
|
|
|
|
if (++this.iteration >= this.limit) {
|
|
|
|
|
// Превышено ограничение по числу итераций
|
|
|
|
|
|
|
|
|
|
// Сброс счётчика итераций
|
|
|
|
|
this.iteration = 0;
|
|
|
|
|
|
|
|
|
|
// Конец выполнения
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (++node.iteration >= node.limit) {
|
|
|
|
|
// Превышено ограничение по числу итераций
|
|
|
|
|
|
|
|
|
|
// Сброс счётчика итераций
|
|
|
|
|
node.iteration = 0;
|
|
|
|
|
|
|
|
|
|
// Конец выполнения
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-22 18:31:26 +07:00
|
|
|
|
// Инициализация универсального буфера
|
|
|
|
|
let buffer;
|
|
|
|
|
|
2022-11-22 05:40:30 +07:00
|
|
|
|
// Инициализация координат целевого узла
|
2022-11-22 18:31:26 +07:00
|
|
|
|
let x1 =
|
|
|
|
|
(isNaN((buffer = parseInt(node.element.style.left))) ? 0 : buffer) +
|
|
|
|
|
node.element.offsetWidth / 2;
|
|
|
|
|
let y1 =
|
|
|
|
|
(isNaN((buffer = parseInt(node.element.style.top))) ? 0 : buffer) +
|
|
|
|
|
node.element.offsetHeight / 2;
|
2022-11-22 05:40:30 +07:00
|
|
|
|
|
|
|
|
|
// Инициализация координат обрабатываемого узла
|
2022-11-22 18:31:26 +07:00
|
|
|
|
let x2 =
|
|
|
|
|
(isNaN((buffer = parseInt(this.element.style.left))) ? 0 : buffer) +
|
|
|
|
|
this.element.offsetWidth / 2;
|
|
|
|
|
let y2 =
|
|
|
|
|
(isNaN((buffer = parseInt(this.element.style.top))) ? 0 : buffer) +
|
|
|
|
|
this.element.offsetHeight / 2;
|
2022-11-22 05:40:30 +07:00
|
|
|
|
|
|
|
|
|
// Реинициализация вектора между узлами
|
2022-11-24 05:04:14 +07:00
|
|
|
|
between = new Victor(x1 - x2, y1 - y2);
|
2022-11-22 05:40:30 +07:00
|
|
|
|
|
2022-11-23 23:50:13 +07:00
|
|
|
|
// Узлы преодолели расстояние столкновения?
|
2022-11-22 05:40:30 +07:00
|
|
|
|
if (
|
2022-11-22 18:31:26 +07:00
|
|
|
|
!node.actions.collision ||
|
2022-11-22 05:40:30 +07:00
|
|
|
|
between.length() > node.diameter / 2 + this.diameter / 2 ||
|
2022-11-22 18:31:26 +07:00
|
|
|
|
--iterations <= 0
|
2022-11-22 05:40:30 +07:00
|
|
|
|
)
|
|
|
|
|
break;
|
|
|
|
|
|
2022-11-22 05:55:45 +07:00
|
|
|
|
// Инициализация координат вектора (узла с которым произошло столкновение)
|
2022-11-24 05:04:14 +07:00
|
|
|
|
let vector = new Victor(x1, y1)
|
|
|
|
|
.add(new Victor(between.x, between.y).norm().unfloat())
|
2022-11-22 05:40:30 +07:00
|
|
|
|
.subtract(
|
2022-11-24 05:04:14 +07:00
|
|
|
|
new Victor(
|
2022-11-22 05:40:30 +07:00
|
|
|
|
node.element.offsetWidth / 2,
|
|
|
|
|
node.element.offsetHeight / 2
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
2022-11-23 23:50:13 +07:00
|
|
|
|
if (node.actions.collision) {
|
|
|
|
|
// Активно столкновение узлов
|
|
|
|
|
|
|
|
|
|
// Запрещение столкновения, притягивания и отталкивания целевого узла и обрабатываемого узла (другими узлами)
|
|
|
|
|
node.actions.collision = node.actions.pushing = node.actions.pulling = this.actions.collision = this.actions.pushing = this.actions.pulling = false;
|
|
|
|
|
|
|
|
|
|
// Запись узлов в реестр задействованных узлов
|
|
|
|
|
involved.add(this);
|
|
|
|
|
|
|
|
|
|
// Перемещение узла
|
|
|
|
|
node.move(vector.x, vector.y, involved, involved, involved);
|
|
|
|
|
|
|
|
|
|
// Разрешение столкновения, притягивания и отталкивания целевого узла и обрабатываемого узла (другими узлами)
|
|
|
|
|
node.actions.collision = node.actions.pushing = node.actions.pulling = this.actions.collision = this.actions.pushing = this.actions.pulling = true;
|
|
|
|
|
}
|
2022-11-22 18:31:26 +07:00
|
|
|
|
|
|
|
|
|
// Проверка на столкновение узлов
|
|
|
|
|
} while (
|
|
|
|
|
node.actions.collision &&
|
|
|
|
|
between.length() <= node.diameter / 2 + this.diameter / 2
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-23 23:50:13 +07:00
|
|
|
|
pushing(nodes, involved, add) {
|
|
|
|
|
if (++this.iteration >= this.limit) {
|
|
|
|
|
// Превышено ограничение по числу итераций
|
|
|
|
|
|
|
|
|
|
// Сброс счётчика итераций
|
|
|
|
|
this.iteration = 0;
|
|
|
|
|
|
|
|
|
|
// Отмена выполнения
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-22 18:31:26 +07:00
|
|
|
|
// Инициализация буфера реестра узлов
|
|
|
|
|
const registry = new Set(nodes);
|
|
|
|
|
|
|
|
|
|
// Удаление текущего узла из буфера
|
|
|
|
|
registry.delete(this);
|
|
|
|
|
|
|
|
|
|
// Инициализация ссылки на ядро
|
|
|
|
|
const _this = this;
|
|
|
|
|
|
2022-11-23 23:50:13 +07:00
|
|
|
|
// Увеличение дистанции для проверки
|
|
|
|
|
const distance = 100;
|
|
|
|
|
|
|
|
|
|
// Обработка отталкивания узлов
|
2022-11-22 18:31:26 +07:00
|
|
|
|
for (const node of registry) {
|
|
|
|
|
// Перебор узлов в буфере реестра
|
|
|
|
|
|
2022-11-23 23:50:13 +07:00
|
|
|
|
// Защита от повторной обработки узла
|
|
|
|
|
if (involved.has(node)) continue;
|
|
|
|
|
|
2022-11-22 18:31:26 +07:00
|
|
|
|
// Инициализация вектора между узлами
|
|
|
|
|
let between;
|
|
|
|
|
|
|
|
|
|
// Инициализация максимального количества итераций
|
2022-11-23 23:50:13 +07:00
|
|
|
|
let iterations = 30;
|
2022-11-22 18:31:26 +07:00
|
|
|
|
|
|
|
|
|
function move() {
|
2022-11-23 23:50:13 +07:00
|
|
|
|
if (++node.iteration >= node.limit) {
|
|
|
|
|
// Превышено ограничение по числу итераций
|
|
|
|
|
|
|
|
|
|
// Сброс счётчика итераций
|
|
|
|
|
node.iteration = 0;
|
|
|
|
|
|
|
|
|
|
// Отмена выполнения
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-22 18:31:26 +07:00
|
|
|
|
// Инициализация универсального буфера
|
|
|
|
|
let buffer;
|
|
|
|
|
|
|
|
|
|
// Инициализация координат целевого узла
|
|
|
|
|
let x1 =
|
|
|
|
|
(isNaN((buffer = parseInt(node.element.style.left))) ? 0 : buffer) +
|
|
|
|
|
node.element.offsetWidth / 2;
|
|
|
|
|
let y1 =
|
|
|
|
|
(isNaN((buffer = parseInt(node.element.style.top))) ? 0 : buffer) +
|
|
|
|
|
node.element.offsetHeight / 2;
|
|
|
|
|
|
|
|
|
|
// Инициализация координат обрабатываемого узла
|
|
|
|
|
let x2 =
|
|
|
|
|
(isNaN((buffer = parseInt(_this.element.style.left)))
|
|
|
|
|
? 0
|
|
|
|
|
: buffer) +
|
|
|
|
|
_this.element.offsetWidth / 2;
|
|
|
|
|
let y2 =
|
|
|
|
|
(isNaN((buffer = parseInt(_this.element.style.top))) ? 0 : buffer) +
|
|
|
|
|
_this.element.offsetHeight / 2;
|
|
|
|
|
|
|
|
|
|
// Реинициализация вектора между узлами
|
2022-11-24 05:04:14 +07:00
|
|
|
|
between = new Victor(x1 - x2, y1 - y2);
|
2022-11-22 18:31:26 +07:00
|
|
|
|
|
2022-11-23 23:50:13 +07:00
|
|
|
|
// Инициализация увеличения
|
|
|
|
|
let increase =
|
|
|
|
|
(node.diameter + _this.diameter) /
|
|
|
|
|
2 ** (_this.increase + node.increase);
|
|
|
|
|
|
|
|
|
|
// Узлы преодолели расстояние отталкивания?
|
2022-11-22 18:31:26 +07:00
|
|
|
|
if (
|
2022-11-23 23:50:13 +07:00
|
|
|
|
!node.actions.pushing ||
|
2022-11-22 18:31:26 +07:00
|
|
|
|
between.length() >
|
2022-11-26 04:38:23 +07:00
|
|
|
|
(node.diameter + _this.diameter) / 2 +
|
|
|
|
|
distance +
|
|
|
|
|
increase +
|
|
|
|
|
(typeof add === "number" ? add : 0) ||
|
2022-11-22 18:31:26 +07:00
|
|
|
|
--iterations <= 0
|
|
|
|
|
)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Инициализация координат вектора (узла с которым произошло столкновение)
|
2022-11-24 05:04:14 +07:00
|
|
|
|
let vector = new Victor(x1, y1)
|
|
|
|
|
.add(new Victor(between.x, between.y).norm().unfloat())
|
2022-11-22 18:31:26 +07:00
|
|
|
|
.subtract(
|
2022-11-24 05:04:14 +07:00
|
|
|
|
new Victor(
|
2022-11-22 18:31:26 +07:00
|
|
|
|
node.element.offsetWidth / 2,
|
|
|
|
|
node.element.offsetHeight / 2
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
2022-11-23 23:50:13 +07:00
|
|
|
|
if (node.actions.pushing) {
|
|
|
|
|
// Активно притягивание узла
|
|
|
|
|
|
|
|
|
|
// Запрещение столкновения, притягивания и отталкивания целевого узла и обрабатываемого узла (другими узлами)
|
|
|
|
|
node.actions.collision = node.actions.pushing = node.actions.pulling = _this.actions.collision = _this.actions.pushing = _this.actions.pulling = false;
|
|
|
|
|
|
|
|
|
|
// Запись узлов в реестр задействованных узлов
|
|
|
|
|
involved.add(_this);
|
|
|
|
|
|
|
|
|
|
// Перемещение узла
|
|
|
|
|
node.move(vector.x, vector.y, involved, involved, involved);
|
|
|
|
|
|
|
|
|
|
// Разрешение столкновения, притягивания и отталкивания целевого узла и обрабатываемого узла (другими узлами)
|
|
|
|
|
node.actions.collision = node.actions.pushing = node.actions.pulling = _this.actions.collision = _this.actions.pushing = _this.actions.pulling = true;
|
|
|
|
|
}
|
2022-11-22 18:31:26 +07:00
|
|
|
|
|
|
|
|
|
// Проверка расстояния
|
|
|
|
|
if (
|
2022-11-23 23:50:13 +07:00
|
|
|
|
node.actions.pushing &&
|
2022-11-22 18:31:26 +07:00
|
|
|
|
between.length() <=
|
2022-11-26 04:38:23 +07:00
|
|
|
|
(node.diameter + _this.diameter) / 2 +
|
|
|
|
|
distance +
|
|
|
|
|
increase +
|
|
|
|
|
(typeof add === "number" ? add : 0)
|
2022-11-22 18:31:26 +07:00
|
|
|
|
)
|
|
|
|
|
setTimeout(move, between.length() / 100);
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-23 23:50:13 +07:00
|
|
|
|
// Повторная обработка (вход в рекурсию)
|
|
|
|
|
if (node.actions.pushing) move();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pulling(nodes, involved, add) {
|
|
|
|
|
// Инициализация буфера реестра узлов
|
|
|
|
|
const registry = new Set(nodes);
|
|
|
|
|
|
|
|
|
|
// Удаление текущего узла из буфера
|
|
|
|
|
registry.delete(this);
|
|
|
|
|
|
|
|
|
|
// Инициализация ссылки на ядро
|
|
|
|
|
const _this = this;
|
|
|
|
|
|
|
|
|
|
// Увеличение дистанции для проверки
|
|
|
|
|
const distance = 150;
|
|
|
|
|
|
|
|
|
|
// Обработка притягивания узлов
|
|
|
|
|
for (const node of registry) {
|
|
|
|
|
// Перебор узлов в буфере реестра
|
|
|
|
|
|
|
|
|
|
// Защита от повторной обработки узла
|
|
|
|
|
if (involved.has(node)) continue;
|
|
|
|
|
|
|
|
|
|
// Инициализация вектора между узлами
|
|
|
|
|
let between;
|
|
|
|
|
|
|
|
|
|
// Инициализация максимального количества итераций
|
|
|
|
|
let iterations = 30;
|
|
|
|
|
|
|
|
|
|
function move() {
|
|
|
|
|
if (++_this.iteration >= _this.limit) {
|
|
|
|
|
// Превышено ограничение по числу итераций
|
|
|
|
|
|
|
|
|
|
// Сброс счётчика итераций
|
|
|
|
|
_this.iteration = 0;
|
|
|
|
|
|
|
|
|
|
// Конец выполнения
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (++node.iteration >= node.limit) {
|
|
|
|
|
// Превышено ограничение по числу итераций
|
|
|
|
|
|
|
|
|
|
// Сброс счётчика итераций
|
|
|
|
|
node.iteration = 0;
|
|
|
|
|
|
|
|
|
|
// Конец выполнения
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Инициализация универсального буфера
|
|
|
|
|
let buffer;
|
|
|
|
|
|
|
|
|
|
// Инициализация координат целевого узла
|
|
|
|
|
let x1 =
|
|
|
|
|
(isNaN((buffer = parseInt(node.element.style.left))) ? 0 : buffer) +
|
|
|
|
|
node.element.offsetWidth / 2;
|
|
|
|
|
let y1 =
|
|
|
|
|
(isNaN((buffer = parseInt(node.element.style.top))) ? 0 : buffer) +
|
|
|
|
|
node.element.offsetHeight / 2;
|
|
|
|
|
|
|
|
|
|
// Инициализация координат обрабатываемого узла
|
|
|
|
|
let x2 =
|
|
|
|
|
(isNaN((buffer = parseInt(_this.element.style.left)))
|
|
|
|
|
? 0
|
|
|
|
|
: buffer) +
|
|
|
|
|
_this.element.offsetWidth / 2;
|
|
|
|
|
let y2 =
|
|
|
|
|
(isNaN((buffer = parseInt(_this.element.style.top))) ? 0 : buffer) +
|
|
|
|
|
_this.element.offsetHeight / 2;
|
|
|
|
|
|
|
|
|
|
// Реинициализация вектора между узлами
|
2022-11-24 05:04:14 +07:00
|
|
|
|
between = new Victor(x1 - x2, y1 - y2);
|
2022-11-23 23:50:13 +07:00
|
|
|
|
|
|
|
|
|
// Инициализация увеличения
|
|
|
|
|
let increase =
|
|
|
|
|
(node.diameter + _this.diameter) /
|
|
|
|
|
2 ** (_this.increase + node.increase);
|
|
|
|
|
|
|
|
|
|
// Узлы преодолели расстояние притягивания?
|
|
|
|
|
if (
|
|
|
|
|
!node.actions.pulling ||
|
|
|
|
|
between.length() <=
|
2022-11-26 04:38:23 +07:00
|
|
|
|
(node.diameter + _this.diameter) / 2 +
|
|
|
|
|
distance +
|
|
|
|
|
increase +
|
|
|
|
|
(typeof add === "number" ? add : 0) ||
|
2022-11-23 23:50:13 +07:00
|
|
|
|
--iterations <= 0
|
|
|
|
|
)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Инициализация координат вектора (узла с которым произошло столкновение)
|
2022-11-24 05:04:14 +07:00
|
|
|
|
let vector = new Victor(x1, y1)
|
|
|
|
|
.add(new Victor(between.x, between.y).norm().invert().unfloat())
|
2022-11-23 23:50:13 +07:00
|
|
|
|
.subtract(
|
2022-11-24 05:04:14 +07:00
|
|
|
|
new Victor(
|
2022-11-23 23:50:13 +07:00
|
|
|
|
node.element.offsetWidth / 2,
|
|
|
|
|
node.element.offsetHeight / 2
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (node.actions.pulling) {
|
|
|
|
|
// Активно притягивание узлов
|
|
|
|
|
|
|
|
|
|
// Запрещение столкновения, притягивания и отталкивания целевого узла и обрабатываемого узла (другими узлами)
|
|
|
|
|
node.actions.collision = node.actions.pushing = node.actions.pulling = _this.actions.collision = _this.actions.pushing = _this.actions.pulling = false;
|
|
|
|
|
|
|
|
|
|
// Запись узлов в реестр задействованных узлов
|
|
|
|
|
involved.add(_this);
|
|
|
|
|
|
|
|
|
|
// Перемещение узла
|
|
|
|
|
node.move(vector.x, vector.y, involved, involved, involved);
|
|
|
|
|
|
|
|
|
|
// Разрешение столкновения, притягивания и отталкивания целевого узла и обрабатываемого узла (другими узлами)
|
|
|
|
|
node.actions.collision = node.actions.pushing = node.actions.pulling = _this.actions.collision = _this.actions.pushing = _this.actions.pulling = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
node.actions.pulling &&
|
|
|
|
|
between.length() >
|
2022-11-26 04:38:23 +07:00
|
|
|
|
(node.diameter + _this.diameter) / 2 +
|
|
|
|
|
distance +
|
|
|
|
|
increase +
|
|
|
|
|
(typeof add === "number" ? add : 0)
|
2022-11-23 23:50:13 +07:00
|
|
|
|
)
|
|
|
|
|
return setTimeout(
|
|
|
|
|
move,
|
|
|
|
|
between.length() / 10 - between.length() / 10
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Повторная обработка (вход в рекурсию)
|
|
|
|
|
if (node.actions.pulling) move();
|
2022-11-22 18:31:26 +07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
configure(attribute) {
|
|
|
|
|
// Инициализация названия параметра
|
|
|
|
|
const parameter = (/^data-graph-(\w+)$/.exec(attribute) ?? [, null])[1];
|
|
|
|
|
|
|
|
|
|
if (typeof parameter === "string") {
|
|
|
|
|
// Параметр найден
|
|
|
|
|
|
|
|
|
|
// Проверка на разрешение изменения
|
|
|
|
|
if (this.#block.has(parameter)) return;
|
|
|
|
|
|
|
|
|
|
// Инициализация значения параметра
|
|
|
|
|
const value = this.element.getAttribute(attribute);
|
|
|
|
|
|
|
|
|
|
if (typeof value !== undefined || typeof value !== null) {
|
|
|
|
|
// Найдено значение
|
|
|
|
|
|
|
|
|
|
// Запрошено изменение координаты: x
|
|
|
|
|
if (parameter === "x") this.element.style.left = value + "px";
|
|
|
|
|
|
|
|
|
|
// Запрошено изменение координаты: y
|
|
|
|
|
if (parameter === "y") this.element.style.top = value + "px";
|
|
|
|
|
|
|
|
|
|
// Инициализация буфера для временных данных
|
|
|
|
|
let buffer;
|
|
|
|
|
|
|
|
|
|
// Запись параметра
|
|
|
|
|
this[parameter] = isNaN((buffer = parseFloat(value)))
|
|
|
|
|
? value === "true"
|
|
|
|
|
? true
|
|
|
|
|
: value === "false"
|
2022-11-26 04:38:23 +07:00
|
|
|
|
? false
|
|
|
|
|
: value
|
2022-11-22 18:31:26 +07:00
|
|
|
|
: buffer;
|
|
|
|
|
}
|
2022-11-22 05:40:30 +07:00
|
|
|
|
}
|
2022-11-01 06:19:17 +07:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2022-11-22 06:43:57 +07:00
|
|
|
|
// Класс узла
|
|
|
|
|
get node() {
|
|
|
|
|
return this.#node;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Класс соединения
|
|
|
|
|
#connection = class connection {
|
|
|
|
|
// HTML-элемент
|
|
|
|
|
#element;
|
|
|
|
|
|
|
|
|
|
// HTML-элемент
|
|
|
|
|
get element() {
|
|
|
|
|
return this.#element;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Инстанция node от которой начинается соединение
|
|
|
|
|
#from;
|
|
|
|
|
|
|
|
|
|
// Инстанция node от которой начинается соединение
|
|
|
|
|
get from() {
|
|
|
|
|
return this.#from;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Инстанция node на которой заканчивается соединение
|
|
|
|
|
#to;
|
|
|
|
|
|
|
|
|
|
// Инстанция node на которой заканчивается соединение
|
|
|
|
|
get to() {
|
|
|
|
|
return this.#to;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Оператор
|
|
|
|
|
#operator;
|
|
|
|
|
|
|
|
|
|
// Оператор
|
|
|
|
|
get operator() {
|
|
|
|
|
return this.#operator;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constructor(operator, from, to) {
|
|
|
|
|
// Запись свойства
|
|
|
|
|
this.#operator = operator;
|
|
|
|
|
|
|
|
|
|
// Запись свойства
|
|
|
|
|
this.#from = from;
|
|
|
|
|
|
|
|
|
|
// Запись свойства
|
|
|
|
|
this.#to = to;
|
|
|
|
|
|
|
|
|
|
// Инициализация оболочки
|
|
|
|
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
|
|
|
svg.id = operator.connections.size;
|
|
|
|
|
svg.classList.add("connection");
|
|
|
|
|
svg.setAttribute("data-from", from.element.id);
|
|
|
|
|
svg.setAttribute("data-to", to.element.id);
|
|
|
|
|
|
|
|
|
|
// Инициализация универсального буфера
|
|
|
|
|
let buffer;
|
|
|
|
|
|
|
|
|
|
// Инициализация оболочки
|
|
|
|
|
const line = document.createElementNS(
|
|
|
|
|
"http://www.w3.org/2000/svg",
|
|
|
|
|
"line"
|
|
|
|
|
);
|
|
|
|
|
line.setAttribute(
|
|
|
|
|
"x1",
|
|
|
|
|
(isNaN((buffer = parseInt(from.element.style.left))) ? 0 : buffer) +
|
2022-11-26 04:38:23 +07:00
|
|
|
|
from.element.offsetWidth / 2
|
2022-11-22 06:43:57 +07:00
|
|
|
|
);
|
|
|
|
|
line.setAttribute(
|
|
|
|
|
"y1",
|
|
|
|
|
(isNaN((buffer = parseInt(from.element.style.top))) ? 0 : buffer) +
|
2022-11-26 04:38:23 +07:00
|
|
|
|
from.element.offsetHeight / 2
|
2022-11-22 06:43:57 +07:00
|
|
|
|
);
|
|
|
|
|
line.setAttribute(
|
|
|
|
|
"x2",
|
|
|
|
|
(isNaN((buffer = parseInt(to.element.style.left))) ? 0 : buffer) +
|
2022-11-26 04:38:23 +07:00
|
|
|
|
to.element.offsetWidth / 2
|
2022-11-22 06:43:57 +07:00
|
|
|
|
);
|
|
|
|
|
line.setAttribute(
|
|
|
|
|
"y2",
|
|
|
|
|
(isNaN((buffer = parseInt(to.element.style.top))) ? 0 : buffer) +
|
2022-11-26 04:38:23 +07:00
|
|
|
|
to.element.offsetHeight / 2
|
2022-11-22 06:43:57 +07:00
|
|
|
|
);
|
|
|
|
|
line.setAttribute("stroke", "grey");
|
|
|
|
|
line.setAttribute("stroke-width", "8px");
|
|
|
|
|
|
|
|
|
|
// Запись свойства
|
|
|
|
|
this.#element = svg;
|
|
|
|
|
|
|
|
|
|
// Запись в оболочку
|
|
|
|
|
svg.append(line);
|
|
|
|
|
|
|
|
|
|
// Запись в документ
|
|
|
|
|
operator.shell.appendChild(svg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2022-11-22 18:31:26 +07:00
|
|
|
|
* Синхронизировать местоположение со связанным узлом
|
2022-11-22 06:43:57 +07:00
|
|
|
|
*
|
|
|
|
|
* @param {node} node Инстанция узла (связанного с соединением)
|
|
|
|
|
*/
|
2022-11-22 18:31:26 +07:00
|
|
|
|
sync(node) {
|
2022-11-22 06:43:57 +07:00
|
|
|
|
// Инициализация названий аттрибутов
|
2022-11-22 18:31:26 +07:00
|
|
|
|
let x = "x",
|
|
|
|
|
y = "y";
|
2022-11-22 06:43:57 +07:00
|
|
|
|
|
|
|
|
|
if (node === this.from) {
|
|
|
|
|
// Исходящее соединение
|
|
|
|
|
|
|
|
|
|
// Запись названий аттрибутов
|
2022-11-22 18:31:26 +07:00
|
|
|
|
x += 1;
|
|
|
|
|
y += 1;
|
2022-11-22 06:43:57 +07:00
|
|
|
|
} else if (node === this.to) {
|
|
|
|
|
// Входящее соединение
|
|
|
|
|
|
|
|
|
|
// Запись названий аттрибутов
|
2022-11-22 18:31:26 +07:00
|
|
|
|
x += 2;
|
|
|
|
|
y += 2;
|
2022-11-22 06:43:57 +07:00
|
|
|
|
} else return;
|
|
|
|
|
|
|
|
|
|
// Инициализация универсального буфера
|
|
|
|
|
let buffer;
|
|
|
|
|
|
|
|
|
|
// Запись отступа (координаты по горизонтали)
|
|
|
|
|
this.element.children[0].setAttribute(
|
|
|
|
|
x,
|
|
|
|
|
(isNaN((buffer = parseInt(node.element.style.left))) ? 0 : buffer) +
|
2022-11-26 04:38:23 +07:00
|
|
|
|
node.element.offsetWidth / 2
|
2022-11-22 06:43:57 +07:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Запись отступа (координаты по вертикали)
|
|
|
|
|
this.element.children[0].setAttribute(
|
|
|
|
|
y,
|
|
|
|
|
(isNaN((buffer = parseInt(node.element.style.top))) ? 0 : buffer) +
|
2022-11-26 04:38:23 +07:00
|
|
|
|
node.element.offsetHeight / 2
|
2022-11-22 06:43:57 +07:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Класс соединения
|
|
|
|
|
get connection() {
|
|
|
|
|
return this.#connection;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-01 06:19:17 +07:00
|
|
|
|
#move = true;
|
|
|
|
|
#camera = true;
|
|
|
|
|
|
|
|
|
|
constructor(shell, camera = true) {
|
|
|
|
|
// Запись оболочки
|
2022-11-24 05:04:14 +07:00
|
|
|
|
if (shell instanceof HTMLElement) this.#shell = shell;
|
2022-11-01 06:19:17 +07:00
|
|
|
|
|
|
|
|
|
// Инициализация ссылки на обрабатываемый объект
|
|
|
|
|
const _this = this;
|
|
|
|
|
|
|
|
|
|
// Перемещение камеры
|
|
|
|
|
if (camera === true) {
|
|
|
|
|
this.shell.onmousedown = function (e) {
|
|
|
|
|
// Начало переноса
|
|
|
|
|
|
|
|
|
|
if (_this.#camera) {
|
|
|
|
|
// Разрешено двигать камеру (оболочку)
|
|
|
|
|
|
|
|
|
|
// Инициализация координат
|
|
|
|
|
const coords = _this.shell.getBoundingClientRect();
|
|
|
|
|
const x = e.pageX - coords.left + pageXOffset;
|
|
|
|
|
const y = e.pageY - coords.top + pageYOffset;
|
|
|
|
|
|
2022-11-22 05:40:30 +07:00
|
|
|
|
// Инициализация функции переноса полотна
|
|
|
|
|
function move(onmousemove) {
|
2022-11-01 06:19:17 +07:00
|
|
|
|
// Запись нового отступа от лева
|
2022-11-22 05:40:30 +07:00
|
|
|
|
_this.shell.style.left = onmousemove.pageX - x + "px";
|
|
|
|
|
|
|
|
|
|
// Запись нового отступа от верха
|
|
|
|
|
_this.shell.style.top = onmousemove.pageY - y + "px";
|
2022-11-01 06:19:17 +07:00
|
|
|
|
}
|
|
|
|
|
|
2022-11-22 05:40:30 +07:00
|
|
|
|
// Запись слушателя события: "перенос полотна"
|
2022-11-01 06:19:17 +07:00
|
|
|
|
document.onmousemove = move;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Конец переноса
|
|
|
|
|
_this.shell.onmouseup = function () {
|
|
|
|
|
document.onmousemove = null;
|
|
|
|
|
_this.shell.onmouseup = null;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2022-11-26 04:38:23 +07:00
|
|
|
|
// Блокировка событий браузера (чтобы не дёргалось)
|
2022-11-01 06:19:17 +07:00
|
|
|
|
_this.shell.ondragstart = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
write = function (data = {}) {
|
|
|
|
|
if (typeof data === "object") {
|
|
|
|
|
// Получен обязательный входной параметр в правильном типе
|
|
|
|
|
|
|
|
|
|
// Инициализация узла
|
2022-11-22 06:43:57 +07:00
|
|
|
|
const node = new this.node(this, data);
|
2022-11-01 06:19:17 +07:00
|
|
|
|
|
|
|
|
|
// Инициализация ссылки на обрабатываемый объект
|
|
|
|
|
const _this = this;
|
|
|
|
|
|
|
|
|
|
// Запрет движения камеры при наведении на узел (чтобы двигать узел)
|
|
|
|
|
node.element.onmouseover = function (e) {
|
|
|
|
|
_this.#camera = false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Снятие запрета движения камеры
|
|
|
|
|
node.element.onmouseout = function (e) {
|
|
|
|
|
_this.#camera = true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (this.#move) {
|
|
|
|
|
// Разрешено перемещать узлы
|
|
|
|
|
|
|
|
|
|
node.element.onmousedown = function (onmousedown) {
|
|
|
|
|
// Начало переноса
|
|
|
|
|
|
|
|
|
|
// Позиционирование над остальными узлами
|
2022-11-26 04:38:23 +07:00
|
|
|
|
node.element.style.zIndex = 5000;
|
2022-11-01 06:19:17 +07:00
|
|
|
|
|
|
|
|
|
if (!_this.#camera) {
|
|
|
|
|
// Запрещено двигать камеру (оболочку)
|
|
|
|
|
|
|
|
|
|
// Инициализация координат
|
|
|
|
|
const n = node.element.getBoundingClientRect();
|
|
|
|
|
const s = _this.shell.getBoundingClientRect();
|
|
|
|
|
|
2022-11-22 05:40:30 +07:00
|
|
|
|
// Инициализация функции переноса узла
|
2022-11-01 06:19:17 +07:00
|
|
|
|
function move(onmousemove) {
|
2022-11-22 18:31:26 +07:00
|
|
|
|
// Запись обработки столкновений и отталкивания
|
2022-11-23 23:50:13 +07:00
|
|
|
|
node.actions.collision = node.actions.pushing = node.actions.pulling = false;
|
2022-11-22 18:31:26 +07:00
|
|
|
|
|
2022-11-22 05:40:30 +07:00
|
|
|
|
// Перемещение
|
|
|
|
|
node.move(
|
2022-11-01 06:19:17 +07:00
|
|
|
|
onmousemove.pageX -
|
2022-11-26 04:38:23 +07:00
|
|
|
|
(onmousedown.pageX - n.left + s.left + pageXOffset),
|
2022-11-22 05:40:30 +07:00
|
|
|
|
onmousemove.pageY -
|
2022-11-26 04:38:23 +07:00
|
|
|
|
(onmousedown.pageY - n.top + s.top + pageYOffset),
|
2022-11-22 18:31:26 +07:00
|
|
|
|
true,
|
2022-11-23 23:50:13 +07:00
|
|
|
|
true,
|
2022-11-22 18:31:26 +07:00
|
|
|
|
true
|
2022-11-22 05:40:30 +07:00
|
|
|
|
);
|
2022-11-01 06:19:17 +07:00
|
|
|
|
}
|
|
|
|
|
|
2022-11-22 05:40:30 +07:00
|
|
|
|
// Запись слушателя события: "перенос узла"
|
2022-11-01 06:19:17 +07:00
|
|
|
|
document.onmousemove = move;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Конец переноса
|
|
|
|
|
node.element.onmouseup = function () {
|
|
|
|
|
// Очистка обработчиков событий
|
|
|
|
|
document.onmousemove = null;
|
|
|
|
|
node.element.onmouseup = null;
|
|
|
|
|
|
2022-11-22 18:31:26 +07:00
|
|
|
|
// Запись обработки столкновений и отталкивания
|
2022-11-23 23:50:13 +07:00
|
|
|
|
node.actions.collision = node.actions.pushing = node.actions.pulling = true;
|
2022-11-22 18:31:26 +07:00
|
|
|
|
|
2022-11-01 06:19:17 +07:00
|
|
|
|
// Позиционирование вместе остальными узлами
|
2022-11-26 04:38:23 +07:00
|
|
|
|
node.element.style.zIndex = null;
|
2022-11-01 06:19:17 +07:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Перещапись событий браузера (чтобы не дёргалось)
|
|
|
|
|
node.element.ondragstart = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Запись в реестр
|
|
|
|
|
this.nodes.add(node);
|
|
|
|
|
|
|
|
|
|
return node;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
connect = function (from, to) {
|
|
|
|
|
if (from instanceof this.node && to instanceof this.node) {
|
|
|
|
|
// Получены обязательные входные параметры в правильном типе
|
|
|
|
|
|
2022-11-22 06:43:57 +07:00
|
|
|
|
// Инициализация соединения
|
|
|
|
|
const connection = new this.connection(this, from, to);
|
2022-11-01 06:19:17 +07:00
|
|
|
|
|
|
|
|
|
// Запись соединений в реестры узлов
|
2022-11-22 06:43:57 +07:00
|
|
|
|
from.outputs.add(connection);
|
|
|
|
|
to.inputs.add(connection);
|
2022-11-01 06:19:17 +07:00
|
|
|
|
|
|
|
|
|
// Запись в реестр ядра
|
2022-11-22 06:43:57 +07:00
|
|
|
|
this.connections.add(connection);
|
2022-11-01 06:19:17 +07:00
|
|
|
|
|
|
|
|
|
// Реинициализация узла-получателя
|
2022-11-22 05:40:30 +07:00
|
|
|
|
to.init(1);
|
2022-11-01 06:19:17 +07:00
|
|
|
|
|
2022-11-22 06:43:57 +07:00
|
|
|
|
return connection;
|
2022-11-01 06:19:17 +07:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
2022-11-22 05:40:30 +07:00
|
|
|
|
|
|
|
|
|
document.dispatchEvent(
|
|
|
|
|
new CustomEvent("graph.loaded", {
|
|
|
|
|
detail: { graph }
|
|
|
|
|
})
|
|
|
|
|
);
|
2022-11-26 06:18:53 +07:00
|
|
|
|
|