graph.mjs/graph.js

1608 lines
61 KiB
JavaScript
Raw Normal View History

import Victor from "https://cdn.skypack.dev/victor@1.1.0";
2022-11-22 05:40:30 +07:00
'use strict';
2022-11-01 06:19:17 +07:00
/**
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
class graph {
2022-12-03 12:05:28 +07:00
// Идентификатор HTML-элемента-оболочки (instanceof HTMLElement)
#id = 'graph';
// Прочитать идентификатор HTML-элемента-оболочки (instanceof HTMLElement)
get id() {
return this.#id;
}
// Классы которые будут записаны в HTML-элементы
classes = {
node: {
shell: ['nodes'],
element: ['node'],
onmouseenter: ['onmouseenter'],
title: ['title'],
cover: ['cover'],
description: {
both: ['description'],
hidden: ['hidden'],
shown: ['shown']
},
close: {
both: ['close'],
hidden: ['hidden'],
shown: ['shown']
},
2022-12-03 12:05:28 +07:00
wrappers: {
both: ['wrapper'],
left: ['left'],
right: ['right']
}
2022-12-03 12:05:28 +07:00
},
connection: {
shell: ['connections'],
element: ['connection']
}
};
2022-11-01 06:19:17 +07:00
// Оболочка (instanceof HTMLElement)
2022-12-03 12:05:28 +07:00
#shell = document.getElementById(this.id);
2022-11-22 05:40:30 +07:00
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
// Статус активации функций взаимодействий узлов
actions = {
collision: false,
pushing: true,
pulling: true
}
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;
// Прочитать оператора
2022-11-22 05:40:30 +07:00
get operator() {
return this.#operator;
}
2022-12-03 12:05:28 +07:00
// HTML-элемент-оболочка
#shell;
// Прочитать HTML-элемент-оболочка
get shell() {
return this.#shell;
}
2022-11-22 05:40:30 +07:00
// HTML-элемент
#element;
2022-12-03 12:05:28 +07:00
// Прочитать HTML-элемент
2022-11-22 05:40:30 +07:00
get element() {
return this.#element;
}
// Наблюдатель
#observer = null;
// Прочитать наблюдатель
2022-11-22 05:40:30 +07:00
get observer() {
return this.#observer;
}
// Реестр запрещённых к изменению параметров
2022-12-03 12:29:29 +07:00
#block = new Set(['events']);
// Прочитать реестр запрещённых к изменению параметров
2022-11-22 05:40:30 +07:00
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;
// Прочитать величину степени увеличения диаметра
2022-11-22 18:31:26 +07:00
get addition() {
return this.#addition;
}
// Величина степени увеличения притягивания и отталкивания
#shift = 0;
// Прочитать величину степени увеличения притягивания и отталкивания
get shift() {
return this.#shift;
}
// Глобальный счётчик итераций
2022-11-23 23:50:13 +07:00
iteration = 0;
// Ограничение максимального количества всех итераций
2022-11-23 23:50:13 +07:00
limit = 3000;
/**
* Обработка событий
*
* max - максимум итераций в процессе
* current - текущая итерация в процессе
* flow - максимум итераций в потоке
*/
2022-11-22 18:31:26 +07:00
actions = {
collision: {
max: 100,
current: 0,
flow: {
medium: 30,
hard: 300
}
},
pushing: {
max: 100,
current: 0,
flow: {
medium: 30,
hard: 300
}
},
pulling: {
max: 100,
current: 0,
flow: {
medium: 30,
hard: 300
}
}
2022-11-22 18:31:26 +07:00
};
/**
* Конструктор узла
*
* @param {object} operator Инстанция оператора (графика)
* @param {object} data Данные для генерации
*/
2022-11-22 06:43:57 +07:00
constructor(operator, data) {
2022-12-03 12:05:28 +07:00
// Запись в свойство
this.#operator = operator;
// Инициализация ссылки на ядро
const _this = this;
// Инициализация HTML-элемента-оболочки узлов
if ((this.#shell = document.getElementById(this.#operator.id + '_nodes')) instanceof HTMLElement);
else {
// Не найден HTML-элемент-оболочки узлов
// Инициализация HTML-элемента-оболочки узлов
const shell = document.createElement('section');
shell.id = this.#operator.id + '_nodes';
shell.classList.add(...this.#operator.classes.node.shell);
// Запись в документ
this.#operator.shell.appendChild(shell);
// Запись в свойство
this.#shell = shell;
}
2022-11-22 18:31:26 +07:00
// Инициализация HTML-элемента узла
2022-12-03 12:29:29 +07:00
const article = document.createElement('article');
2022-12-03 12:05:28 +07:00
article.id = this.#operator.id + '_node_' + this.#operator.nodes.size;
2022-12-03 12:08:21 +07:00
if (typeof data.color === 'string') article.classList.add(data.color);
article.classList.add(..._this.operator.classes.node.element);
2022-12-03 12:29:29 +07:00
if (typeof data.href === 'string') {
article.href = data.href;
}
2022-11-26 23:40:46 +07:00
// Запись анимации "выделение обводкой" (чтобы не проигрывалась при открытии страницы)
article.onmouseenter = fn => {
// Запись класса с анимацией
2022-12-03 12:05:28 +07:00
article.classList.add(..._this.#operator.classes.node.onmouseenter);
2022-11-26 23:40:46 +07:00
};
// Инициализация заголовка
2022-12-03 12:29:29 +07:00
const title = document.createElement('h4');
2022-12-03 12:05:28 +07:00
title.classList.add(..._this.#operator.classes.node.title);
2022-11-26 05:04:45 +07:00
title.innerText = data.title ?? '';
2022-11-01 06:19:17 +07:00
// Запись в оболочку
article.appendChild(title);
// Инициализация описания
2022-12-03 12:29:29 +07:00
const description = document.createElement('div');
description.classList.add(..._this.#operator.classes.node.description.both, ..._this.#operator.classes.node.description.hidden);
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 23:40:46 +07:00
// Запись анимации "выделение обводкой" (чтобы не проигрывалась при открытии страницы)
description.onmouseenter = fn => {
// Запись класса с анимацией
2022-12-03 12:05:28 +07:00
description.classList.add(..._this.#operator.classes.node.onmouseenter);
2022-11-26 23:40:46 +07:00
};
// Запись блокировки открытия описания в случае, если был перемещён узел
title.onmousedown = (onmousedown) => {
2022-11-26 06:18:53 +07:00
// Инициализация координат
let x = onmousedown.pageX;
let y = onmousedown.pageY;
// Запись события открытия описания
title.onclick = (onclick) => {
// Отображение описания
2022-12-03 12:05:28 +07:00
_this.show();
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;
}
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) {
// Запись иконки курсора
title.style.cursor = 'grabbing';
2022-11-26 06:18:53 +07:00
// Запись события для переноса узла
title.onclick = (onclick) => {
// Удаление событий
2022-11-26 06:18:53 +07:00
title.onclick = title.onmousemove = null;
// Реинициализация координат
x = onclick.pageX;
y = onclick.pageY;
// Удаление иконки курсора
title.style.cursor = null;
2022-11-26 06:18:53 +07:00
return false;
}
2022-11-26 06:18:53 +07:00
} else {
// Запись события открытия описания
title.onclick = (onclick) => {
// Отображение описания
2022-12-03 12:05:28 +07:00
_this.show();
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;
};
}
}
};
// Запись в оболочку
article.appendChild(description);
// Инициализация левой фигуры для обёртки текста
2022-12-03 12:29:29 +07:00
const left = document.createElement('span');
2022-12-03 12:05:28 +07:00
left.classList.add(..._this.#operator.classes.node.wrappers.both, ..._this.#operator.classes.node.wrappers.left);
// Запись в описание
description.appendChild(left);
// Инициализация правой фигуры для обёртки текста
2022-12-03 12:29:29 +07:00
const right = document.createElement('span');
2022-12-03 12:05:28 +07:00
right.classList.add(..._this.#operator.classes.node.wrappers.both, ..._this.#operator.classes.node.wrappers.right);
// Запись в описание
description.appendChild(right);
// Инициализация ссылки на источник
2022-12-03 12:29:29 +07:00
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;
// Блокировка событий браузера (чтобы не мешать переноса узла)
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;
}
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) {
// Запись иконки курсора
a.style.cursor = 'grabbing';
2022-11-26 06:18:53 +07:00
// Запись события для переноса узла
a.onclick = (onclick) => {
// Удаление событий
2022-11-26 06:18:53 +07:00
a.onclick = a.onmousemove = null;
// Реинициализация координат
x = onclick.pageX;
y = onclick.pageY;
// Удаление иконки курсора
a.style.cursor = null;
2022-11-26 06:18:53 +07:00
return false;
}
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 06:18:53 +07:00
// Удаление иконки курсора
a.style.cursor = null;
return true;
};
}
}
};
// Запись в описание
description.appendChild(a);
// Запись текста в описание
2022-12-03 12:29:29 +07:00
const text = document.createElement('p');
2022-11-26 05:04:45 +07:00
if (typeof data.description === 'string') text.innerText = data.description;
// Запись в оболочку
description.appendChild(text);
if (
2022-12-03 12:29:29 +07:00
typeof data.cover === 'string'
) {
2022-11-26 05:04:45 +07:00
// Получено изображение-обложка
2022-11-26 05:04:45 +07:00
// Инициализация изображения-обложки
2022-12-03 12:29:29 +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-12-03 12:05:28 +07:00
cover.classList.add(..._this.#operator.classes.node.cover);
// Запись в описание
description.appendChild(cover);
}
if (
2022-12-03 12:29:29 +07:00
typeof data.append === 'HTMLCollection' ||
typeof data.append === 'HTMLElement'
) {
// Получены другие HTML-элементы
2022-11-01 06:19:17 +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(..._this.#operator.classes.node.close.both, ..._this.#operator.classes.node.close.hidden);
2022-11-26 05:50:15 +07:00
2022-11-26 22:48:25 +07:00
// Запись блокировки закрытия в случае, если был перемещён узел
close.onmousedown = (onmousedown) => {
// Инициализация координат
let x = onmousedown.pageX;
let y = onmousedown.pageY;
// Запись события открытия описания
close.onclick = (onclick) => {
// Скрытие описания
2022-12-03 12:05:28 +07:00
_this.hide();
2022-11-26 22:48:25 +07:00
// Удаление событий
close.onclick = close.onmousemove = null;
// Реинициализация координат
x = onclick.pageX;
y = onclick.pageY;
// Удаление иконки курсора
close.style.cursor = null;
return true;
}
close.onmousemove = (onmousemove) => {
// Курсор сдвинут более чем на 15 пикселей?
if (Math.abs(x - onmousemove.pageX) > 15 || Math.abs(y - onmousemove.pageY) > 15) {
// Запись иконки курсора
close.style.cursor = 'grabbing';
// Запись события для переноса узла
close.onclick = (onclick) => {
// Удаление событий
close.onclick = close.onmousemove = null;
// Реинициализация координат
x = onclick.pageX;
y = onclick.pageY;
// Удаление иконки курсора
close.style.cursor = null;
return false;
}
} else {
// Запись события открытия описания
close.onclick = (onclick) => {
// Скрытие описания
2022-12-03 12:05:28 +07:00
_this.hide();
2022-11-26 22:48:25 +07:00
// Удаление событий
close.onclick = close.onmousemove = null;
// Реинициализация координат
x = onclick.pageX;
y = onclick.pageY;
// Удаление иконки курсора
close.style.cursor = null;
return true;
};
}
}
};
2022-11-26 05:50:15 +07:00
// Запись в оболочку
article.appendChild(close);
2022-11-22 18:31:26 +07:00
// Запись в документ
2022-12-03 12:05:28 +07:00
this.#shell.appendChild(article);
2022-11-22 18:31:26 +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';
2022-11-26 05:50:15 +07:00
/**
* Показать описание
*/
this.show = fn => {
// Отображение описания
description.classList.add(..._this.#operator.classes.node.description.shown);
description.classList.remove(..._this.#operator.classes.node.description.hidden);
// Отображение кнопки закрытия
close.classList.add(..._this.#operator.classes.node.close.shown);
close.classList.remove(..._this.#operator.classes.node.close.hidden);
2022-11-26 05:50:15 +07:00
// Сдвиг кнопки закрытия описания
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;
// Инициализация сдвига отталкивания и притяжения соединённых узлов
_this.#shift = description.offsetWidth - article.offsetWidth;
// Обработка сдвига
_this.move(null, null, _this.#operator.actions.collision, _this.#operator.actions.pushing, _this.#operator.actions.pulling, true, true);
2022-11-26 05:50:15 +07:00
}
/**
* Скрыть описание
*/
this.hide = fn => {
// Скрытие описания
description.classList.add(..._this.#operator.classes.node.description.hidden);
description.classList.remove(..._this.#operator.classes.node.description.shown);
// Скрытие кнопки закрытия
close.classList.add(..._this.#operator.classes.node.close.hidden);
close.classList.remove(..._this.#operator.classes.node.close.shown);
2022-11-26 05:50:15 +07:00
// Удаление всех изменённых аттрибутов
close.style.top = close.style.right = article.style.zIndex = close.style.zIndex = close.style.scale = close.style.opacity = null;
// Деинициализация сдвига отталкивания и притяжения соединённых узлов
_this.#shift = 0;
// Обработка сдвига
_this.move(null, null, _this.#operator.actions.collision, _this.#operator.actions.pushing, _this.#operator.actions.pulling, true, true);
2022-11-26 05:50:15 +07:00
}
2022-11-01 06:19:17 +07:00
// Запись в свойство
this.#element = article;
2022-11-22 05:40:30 +07:00
2022-11-01 06:19:17 +07:00
// Инициализация
this.init();
2022-11-22 18:31:26 +07:00
// Перемещение
this.move(
2022-12-03 12:05:28 +07:00
this.#operator.shell.offsetWidth / 2 -
this.#diameter / 2 +
(0.5 - Math.random()) * 500,
2022-12-03 12:05:28 +07:00
this.#operator.shell.offsetHeight / 2 -
this.#diameter / 2 +
(0.5 - Math.random()) * 500,
this.#operator.actions.collision,
this.#operator.actions.pushing,
this.#operator.actions.pulling,
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-12-03 12:29:29 +07:00
this.#diameter + 'px';
2022-11-22 05:40:30 +07:00
// Инициализация описания
const description = this.element.getElementsByClassName('description')[0];
// Запись отступа описания (чтобы был по центру узла)
description.style.marginLeft = description.style.marginTop = (this.element.offsetWidth - description.offsetWidth) / 2 + 'px';
2022-11-22 05:40:30 +07:00
// Инициализация ссылки на ядро
const _this = this;
// Инициализация наблюдателя
this.#observer = new MutationObserver(function (mutations) {
for (const mutation of mutations) {
2022-12-03 12:29:29 +07:00
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
});
}
/**
* Переместить узел
*
* @param {*} x Координата X (относительно левого верхнего края)
* @param {*} y Координата Y (относительно левого верхнего края)
* @param {*} collision Активировать столкновение?
* @param {*} pushing Активировать отталкивание?
* @param {*} pulling Активировать притягивание?
* @param {*} reset Сбросить счётчик итераций для процесса?
* @param {*} hard Увеличить количество итераций для процесса?
*/
move(x, y, collision = false, pushing = false, pulling = false, reset = false, hard = false) {
// Сброс счётчика итераций для процесса (реинициализация процесса)
if (reset) this.actions.collision.current = this.actions.pushing.current = this.actions.pulling.current = 0;
// Проверка входящих параметров
if (typeof x !== 'number') x = this.element.getAttribute('data-x') ?? 0;
if (typeof y !== 'number') y = this.element.getAttribute('data-y') ?? 0;
2022-11-22 05:40:30 +07:00
// Запись отступов
2022-12-03 12:29:29 +07:00
this.element.style.left = x + 'px';
this.element.style.top = y + 'px';
2022-11-22 05:40:30 +07:00
// Запись аттрибутов с координатами
2022-12-03 12:29:29 +07:00
this.element.setAttribute('data-x', x);
this.element.setAttribute('data-y', y);
2022-11-22 05:40:30 +07:00
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, hard);
2022-11-23 23:50:13 +07:00
// Инициализация буфера реестра узлов
2022-12-03 12:05:28 +07:00
const registry = new Set(this.#operator.nodes);
2022-11-23 23:50:13 +07:00
if (pushing && !pushing.has(this)) {
// Активно отталкивание
for (const connection of this.inputs) {
// Перебор входящих соединений
// Ограничение выполнения
if (++this.actions.pushing.current >= this.actions.pushing.max) break;
2022-11-23 23:50:13 +07:00
// Защита от повторной обработки
if (pushing.has(connection.from)) continue;
// Удаление из буфера реестра узлов
registry.delete(connection.from);
// Обработка отталкивания
this.pushing(new Set([connection.from]), pushing, 0, hard);
2022-11-23 23:50:13 +07:00
}
for (const connection of this.outputs) {
// Перебор исходящих соединений
// Ограничение выполнения
if (++this.actions.pushing.current >= this.actions.pushing.max) break;
2022-11-23 23:50:13 +07:00
// Защита от повторной обработки
if (pushing.has(connection.to)) continue;
// Удаление из буфера реестра узлов
registry.delete(connection.to);
// Обработка отталкивания
this.pushing(new Set([connection.to]), pushing, 0, hard);
2022-11-23 23:50:13 +07:00
}
}
if (pulling && !pulling.has(this)) {
// Активно притягивание
for (const connection of this.inputs) {
// Перебор входящих соединений
// Ограничение выполнения
if (++this.actions.pulling.current >= this.actions.pulling.max) break;
2022-11-23 23:50:13 +07:00
// Защита от повторной обработки
if (pulling.has(connection.from)) continue;
// Удаление из буфера реестра узлов
registry.delete(connection.from);
// Обработка притягивания
this.pulling(new Set([connection.from]), pulling, 0, hard);
2022-11-23 23:50:13 +07:00
}
for (const connection of this.outputs) {
// Перебор входящих соединений
// Ограничение выполнения
if (++this.actions.pulling.current >= this.actions.pulling.max) break;
2022-11-23 23:50:13 +07:00
// Защита от повторной обработки
if (pulling.has(connection.to)) continue;
// Удаление из буфера реестра узлов
registry.delete(connection.to);
// Обработка притягивания
this.pulling(new Set([connection.to]), pulling, 0, hard);
2022-11-23 23:50:13 +07:00
}
}
// Обработка отталкивания остальных узлов
if (pushing) this.pushing(registry, pushing);
// Синхронизация местоположения исходящих соединений
2022-12-03 12:05:28 +07:00
for (const connection of this.outputs) connection.synchronize(this);
2022-11-22 05:40:30 +07:00
2022-11-22 18:31:26 +07:00
// Синхронизация местоположения входящих соединений
2022-12-03 12:05:28 +07:00
for (const connection of this.inputs) connection.synchronize(this);
2022-11-22 05:40:30 +07:00
}
collision(nodes, involved, hard = false) {
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 (typeof involved === 'object' && involved.has(node)) continue;
2022-11-23 23:50:13 +07:00
2022-11-22 05:40:30 +07:00
// Инициализация вектора между узлами
let between;
// Инициализация ускорения
let increase = 0;
// Инициализация счётчика итераций
let iterations = 0;
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
// Реинициализация вектора между узлами
between = new Victor(x1 - x2, y1 - y2);
2022-11-22 05:40:30 +07:00
// Узлы преодолели расстояние столкновения? (ограничение выполнения)
2022-11-22 05:40:30 +07:00
if (
this.actions.collision.current >= this.actions.collision.max ||
between.length() > node.diameter / 2 + this.diameter / 2 ||
++iterations > (hard ? this.actions.collision.flow.hard : this.actions.collision.flow.medium)
2022-11-22 05:40:30 +07:00
)
break;
// Инициализация координат вектора (узла с которым произошло столкновение)
let vector = new Victor(x1, y1)
.add(new Victor(between.x, between.y).norm().unfloat())
2022-11-22 05:40:30 +07:00
.subtract(
new Victor(
2022-11-22 05:40:30 +07:00
node.element.offsetWidth / 2,
node.element.offsetHeight / 2
)
);
if (this.actions.collision.current < this.actions.collision.max) {
2022-11-23 23:50:13 +07:00
// Активно столкновение узлов
// Запись значений столкновения, притягивания и отталкивания целевого узла и обрабатываемого узла (другими узлами) в буферы
const _node_collision = node.actions.collision.current;
const _node_pushing = node.actions.pushing.current;
const _node_pulling = node.actions.pulling.current;
const _this_collision = this.actions.collision.current;
const _this_pushing = this.actions.pushing.current;
const _this_pulling = this.actions.pulling.current;
2022-11-23 23:50:13 +07:00
// Запрещение столкновения, притягивания и отталкивания целевого узла и обрабатываемого узла (другими узлами)
node.actions.collision.current = node.actions.pushing.current = node.actions.pulling.current = this.actions.collision.current = this.actions.pushing.current = this.actions.pulling.current = 0;
2022-11-23 23:50:13 +07:00
// Запись узлов в реестр задействованных узлов
involved.add(this);
// Перемещение узла
node.move(vector.x, vector.y, involved, involved, involved);
// Возвращение значений столкновения, притягивания и отталкивания целевого узла и обрабатываемого узла (другими узлами)
node.actions.collision.current = _node_collision;
node.actions.pushing.current = _node_pushing;
node.actions.pulling.current = _node_pulling;
this.actions.collision.current = _this_collision;
this.actions.pushing.current = _this_pushing;
this.actions.pulling.current = _this_pulling;
2022-11-23 23:50:13 +07:00
}
2022-11-22 18:31:26 +07:00
// Проверка на столкновение узлов
} while (
this.actions.collision.current < this.actions.collision.max &&
2022-11-22 18:31:26 +07:00
between.length() <= node.diameter / 2 + this.diameter / 2
);
}
}
pushing(nodes = [], involved, add, hard = false) {
2022-11-23 23:50:13 +07:00
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 (typeof involved === 'object' && involved.has(node)) continue;
2022-11-23 23:50:13 +07:00
2022-11-22 18:31:26 +07:00
// Инициализация вектора между узлами
let between;
// Инициализация счётчика итераций
let iterations = 0;
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;
// Реинициализация вектора между узлами
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 =
_this.shift + node.shift +
(_this.diameter + node.diameter) /
2022-11-23 23:50:13 +07:00
2 ** (_this.increase + node.increase);
// Узлы преодолели расстояние отталкивания?
2022-11-22 18:31:26 +07:00
if (
_this.actions.pushing.current >= _this.actions.pushing.max ||
between.length() >
(node.diameter + _this.diameter) / 2 +
distance +
increase +
(typeof add === 'number' ? add : 0) ||
++iterations > (hard ? _this.actions.pushing.flow.hard : _this.actions.pushing.flow.medium)
2022-11-22 18:31:26 +07:00
)
return;
// Инициализация координат вектора (узла с которым произошло столкновение)
let vector = new Victor(x1, y1)
.add(new Victor(between.x, between.y).norm().unfloat())
2022-11-22 18:31:26 +07:00
.subtract(
new Victor(
2022-11-22 18:31:26 +07:00
node.element.offsetWidth / 2,
node.element.offsetHeight / 2
)
);
if (_this.actions.pushing.current < _this.actions.pushing.max) {
2022-11-23 23:50:13 +07:00
// Активно притягивание узла
// Запись значений столкновения, притягивания и отталкивания целевого узла и обрабатываемого узла (другими узлами) в буферы
const _node_collision = node.actions.collision.current;
const _node_pushing = node.actions.pushing.current;
const _node_pulling = node.actions.pulling.current;
const _this_collision = _this.actions.collision.current;
const _this_pushing = _this.actions.pushing.current;
const _this_pulling = _this.actions.pulling.current;
2022-11-23 23:50:13 +07:00
// Запрещение столкновения, притягивания и отталкивания целевого узла и обрабатываемого узла (другими узлами)
node.actions.collision.current = node.actions.pushing.current = node.actions.pulling.current = _this.actions.collision.current = _this.actions.pushing.current = _this.actions.pulling.current = 0;
2022-11-23 23:50:13 +07:00
// Запись узлов в реестр задействованных узлов
involved.add(_this);
// Перемещение узла
node.move(vector.x, vector.y, involved, involved, involved);
// Возвращение значений столкновения, притягивания и отталкивания целевого узла и обрабатываемого узла (другими узлами)
node.actions.collision.current = _node_collision;
node.actions.pushing.current = _node_pushing;
node.actions.pulling.current = _node_pulling;
_this.actions.collision.current = _this_collision;
_this.actions.pushing.current = _this_pushing;
_this.actions.pulling.current = _this_pulling;
2022-11-23 23:50:13 +07:00
}
2022-11-22 18:31:26 +07:00
// Проверка расстояния
if (
_this.actions.pushing.current < _this.actions.pushing.max &&
2022-11-22 18:31:26 +07:00
between.length() <=
(node.diameter + _this.diameter) / 2 +
distance +
increase +
2022-12-03 12:29:29 +07:00
(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 (_this.actions.pushing.current < _this.actions.pushing.max) move();
2022-11-23 23:50:13 +07:00
}
}
pulling(nodes = [], involved, add, hard = false) {
2022-11-23 23:50:13 +07:00
// Инициализация буфера реестра узлов
const registry = new Set(nodes);
// Удаление текущего узла из буфера
registry.delete(this);
// Инициализация ссылки на ядро
const _this = this;
// Увеличение дистанции для проверки !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
2022-11-23 23:50:13 +07:00
const distance = 150;
// Обработка притягивания узлов
for (const node of registry) {
// Перебор узлов в буфере реестра
// Защита от повторной обработки узла
if (typeof involved === 'object' && involved.has(node)) continue;
2022-11-23 23:50:13 +07:00
// Инициализация вектора между узлами
let between;
// Инициализация счётчика итераций
let iterations = 0;
2022-11-23 23:50:13 +07:00
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;
// Реинициализация вектора между узлами
between = new Victor(x1 - x2, y1 - y2);
2022-11-23 23:50:13 +07:00
// Инициализация увеличения
let increase =
_this.shift + node.shift +
2022-11-23 23:50:13 +07:00
(node.diameter + _this.diameter) /
2 ** (_this.increase + node.increase);
// Узлы преодолели расстояние притягивания?
if (
_this.actions.pulling.current >= _this.actions.pulling.max ||
between.length() <=
(node.diameter + _this.diameter) / 2 +
distance +
increase +
(typeof add === 'number' ? add : 0) ||
++iterations > (hard ? _this.actions.pulling.flow.hard : _this.actions.pulling.flow.medium)
2022-11-23 23:50:13 +07:00
)
return;
// Инициализация координат вектора (узла с которым произошло столкновение)
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(
new Victor(
2022-11-23 23:50:13 +07:00
node.element.offsetWidth / 2,
node.element.offsetHeight / 2
)
);
if (_this.actions.pulling.current < _this.actions.pulling.max) {
2022-11-23 23:50:13 +07:00
// Активно притягивание узлов
// Запись значений столкновения, притягивания и отталкивания целевого узла и обрабатываемого узла (другими узлами) в буферы
const _node_collision = node.actions.collision.current;
const _node_pushing = node.actions.pushing.current;
const _node_pulling = node.actions.pulling.current;
const _this_collision = _this.actions.collision.current;
const _this_pushing = _this.actions.pushing.current;
const _this_pulling = _this.actions.pulling.current;
2022-11-23 23:50:13 +07:00
// Запрещение столкновения, притягивания и отталкивания целевого узла и обрабатываемого узла (другими узлами)
node.actions.collision.current = node.actions.pushing.current = node.actions.pulling.current = _this.actions.collision.current = _this.actions.pushing.current = _this.actions.pulling.current = 0;
2022-11-23 23:50:13 +07:00
// Запись узлов в реестр задействованных узлов
involved.add(_this);
// Перемещение узла
node.move(vector.x, vector.y, involved, involved, involved);
// Возвращение значений столкновения, притягивания и отталкивания целевого узла и обрабатываемого узла (другими узлами)
node.actions.collision.current = _node_collision;
node.actions.pushing.current = _node_pushing;
node.actions.pulling.current = _node_pulling;
_this.actions.collision.current = _this_collision;
_this.actions.pushing.current = _this_pushing;
_this.actions.pulling.current = _this_pulling;
2022-11-23 23:50:13 +07:00
}
if (
_this.actions.pulling.current < _this.actions.pulling.max &&
2022-11-23 23:50:13 +07:00
between.length() >
(node.diameter + _this.diameter) / 2 +
distance +
increase +
2022-12-03 12:29:29 +07:00
(typeof add === 'number' ? add : 0)
2022-11-23 23:50:13 +07:00
)
return setTimeout(
move,
between.length() / 10 - between.length() / 10
);
}
// Повторная обработка (вход в рекурсию)
if (_this.actions.pulling.current < _this.actions.pulling.max) move();
2022-11-22 18:31:26 +07:00
}
}
configure(attribute) {
// Инициализация названия параметра
2022-12-03 12:29:29 +07:00
const parameter = (/^data-(\w+)$/.exec(attribute) ?? [, null])[1];
2022-11-22 18:31:26 +07:00
2022-12-03 12:29:29 +07:00
if (typeof parameter === 'string') {
2022-11-22 18:31:26 +07:00
// Параметр найден
// Проверка на разрешение изменения
if (this.#block.has(parameter)) return;
// Инициализация значения параметра
const value = this.element.getAttribute(attribute);
if (typeof value !== undefined || typeof value !== null) {
// Найдено значение
// Запрошено изменение координаты: x
2022-12-03 12:29:29 +07:00
if (parameter === 'x') this.element.style.left = value + 'px';
2022-11-22 18:31:26 +07:00
// Запрошено изменение координаты: y
2022-12-03 12:29:29 +07:00
if (parameter === 'y') this.element.style.top = value + 'px';
2022-11-22 18:31:26 +07:00
// Инициализация буфера для временных данных
let buffer;
// Запись параметра
this[parameter] = isNaN((buffer = parseFloat(value)))
2022-12-03 12:29:29 +07:00
? value === 'true'
2022-11-22 18:31:26 +07:00
? true
2022-12-03 12:29:29 +07:00
: value === 'false'
? 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-12-03 12:05:28 +07:00
// Прочитать класс узла
2022-11-22 06:43:57 +07:00
get node() {
return this.#node;
}
// Класс соединения
#connection = class connection {
2022-12-03 12:05:28 +07:00
// HTML-элемент-оболочка
#shell;
// Прочитать HTML-элемент-оболочку
get shell() {
return this.#shell;
}
// HTML-элемент соединения
2022-11-22 06:43:57 +07:00
#element;
2022-12-03 12:05:28 +07:00
// Прочитать HTML-элемент соединения
2022-11-22 06:43:57 +07:00
get element() {
return this.#element;
}
2022-12-03 12:05:28 +07:00
// Инстанция this.operator.node от которой начинается соединение
2022-11-22 06:43:57 +07:00
#from;
2022-12-03 12:05:28 +07:00
// Прочитать инстанцию this.operator.node от которой начинается соединение
2022-11-22 06:43:57 +07:00
get from() {
return this.#from;
}
2022-12-03 12:05:28 +07:00
// Инстанция this.operator.node на которой заканчивается соединение
2022-11-22 06:43:57 +07:00
#to;
2022-12-03 12:05:28 +07:00
// Прочитать инстанцию this.operator.node на которой заканчивается соединение
2022-11-22 06:43:57 +07:00
get to() {
return this.#to;
}
// Оператор
#operator;
2022-12-03 12:05:28 +07:00
// Прочитать оператора
2022-11-22 06:43:57 +07:00
get operator() {
return this.#operator;
}
/**
* Конструктор соединения
*
* @param {object} operator Инстанция оператора (графика)
* @param {object} from Инстанция узла от которого идёт соединение
* @param {object} to Инстанция узла к которому идёт соединения
*/
2022-11-22 06:43:57 +07:00
constructor(operator, from, to) {
// Запись свойства
this.#operator = operator;
// Запись свойства
this.#from = from;
// Запись свойства
this.#to = to;
2022-12-03 12:05:28 +07:00
// Инициализация HTML-элемента-оболочки соединений
if ((this.#shell = document.getElementById(this.#operator.id + '_connections')) instanceof SVGElement);
else {
// Не найден HTML-элемент-оболочки соединений
// Инициализация HTML-элемента-оболочки соединений
2022-12-03 12:29:29 +07:00
const shell = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
2022-12-03 12:05:28 +07:00
shell.id = this.#operator.id + '_connections';
shell.classList.add(...this.#operator.classes.connection.shell);
// Запись в документ
this.#operator.shell.appendChild(shell);
// Запись в свойство
this.#shell = shell;
}
2022-11-22 06:43:57 +07:00
// Инициализация универсального буфера
let buffer;
// Инициализация оболочки
const line = document.createElementNS(
2022-12-03 12:29:29 +07:00
'http://www.w3.org/2000/svg',
'line'
2022-11-22 06:43:57 +07:00
);
line.setAttribute(
2022-12-03 12:29:29 +07:00
'x1',
2022-11-22 06:43:57 +07:00
(isNaN((buffer = parseInt(from.element.style.left))) ? 0 : buffer) +
from.element.offsetWidth / 2
2022-11-22 06:43:57 +07:00
);
line.setAttribute(
2022-12-03 12:29:29 +07:00
'y1',
2022-11-22 06:43:57 +07:00
(isNaN((buffer = parseInt(from.element.style.top))) ? 0 : buffer) +
from.element.offsetHeight / 2
2022-11-22 06:43:57 +07:00
);
line.setAttribute(
2022-12-03 12:29:29 +07:00
'x2',
2022-11-22 06:43:57 +07:00
(isNaN((buffer = parseInt(to.element.style.left))) ? 0 : buffer) +
to.element.offsetWidth / 2
2022-11-22 06:43:57 +07:00
);
line.setAttribute(
2022-12-03 12:29:29 +07:00
'y2',
2022-11-22 06:43:57 +07:00
(isNaN((buffer = parseInt(to.element.style.top))) ? 0 : buffer) +
to.element.offsetHeight / 2
2022-11-22 06:43:57 +07:00
);
2022-12-03 12:29:29 +07:00
line.setAttribute('stroke', 'grey');
line.setAttribute('stroke-width', '8px');
2022-12-03 12:05:28 +07:00
line.id = this.#operator.id + '_connection_' + operator.connections.size;
line.classList.add(...this.operator.classes.connection.element);
2022-12-03 12:29:29 +07:00
line.setAttribute('data-from', from.element.id);
line.setAttribute('data-to', to.element.id);
2022-11-22 06:43:57 +07:00
// Запись в оболочку
2022-12-03 12:05:28 +07:00
this.shell.append(line);
2022-11-22 06:43:57 +07:00
2022-12-03 12:05:28 +07:00
// Запись в свойство
this.#element = line;
2022-11-22 06:43:57 +07:00
}
/**
2022-11-22 18:31:26 +07:00
* Синхронизировать местоположение со связанным узлом
2022-11-22 06:43:57 +07:00
*
* @param {node} node Инстанция узла (связанного с соединением)
*/
2022-12-03 12:05:28 +07:00
synchronize(node) {
2022-11-22 06:43:57 +07:00
// Инициализация названий аттрибутов
2022-12-03 12:29:29 +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;
// Запись отступа (координаты по горизонтали)
2022-12-03 12:05:28 +07:00
this.element.setAttribute(
2022-11-22 06:43:57 +07:00
x,
2022-12-03 12:29:29 +07:00
-this.#shell.getAttribute('data-x') +
2022-11-22 06:43:57 +07:00
(isNaN((buffer = parseInt(node.element.style.left))) ? 0 : buffer) +
node.element.offsetWidth / 2
2022-11-22 06:43:57 +07:00
);
// Запись отступа (координаты по вертикали)
2022-12-03 12:05:28 +07:00
this.element.setAttribute(
2022-11-22 06:43:57 +07:00
y,
2022-12-03 12:29:29 +07:00
-this.#shell.getAttribute('data-y') +
2022-11-22 06:43:57 +07:00
(isNaN((buffer = parseInt(node.element.style.top))) ? 0 : buffer) +
node.element.offsetHeight / 2
2022-11-22 06:43:57 +07:00
);
}
};
2022-12-03 12:05:28 +07:00
// Прочитать класс соединения
2022-11-22 06:43:57 +07:00
get connection() {
return this.#connection;
}
2022-12-03 12:05:28 +07:00
// Разрешено перемещать узлы?
2022-11-01 06:19:17 +07:00
#move = true;
2022-12-03 12:05:28 +07:00
// Разрешено перемещать камеру? (svg-элементы-соединения - рёбра)
2022-11-01 06:19:17 +07:00
#camera = true;
/**
* Конструктор графика
*
* @param {HTMLElement|string} shell HTML-элемент-оболочка для графика, либо его идентификатор
* @param {boolean} body Перенос работает на теле документа? (иначе на HTML-элементе-оболочке)
* @param {boolean} camera Активировать перемещение камеры?
*/
2022-12-03 12:05:28 +07:00
constructor(shell, body = true, camera = true) {
2022-11-01 06:19:17 +07:00
// Запись оболочки
if (shell instanceof HTMLElement) this.#shell = shell;
2022-12-03 12:05:28 +07:00
else if (typeof shell === 'string') this.#shell = document.getElementById(shell);
// Проверка на инициализированность HTML-элемента-оболочки
if (typeof this.#shell === undefined) return false;
// Запись идентификатора
this.#id = this.#shell.id;
2022-11-01 06:19:17 +07:00
// Инициализация ссылки на обрабатываемый объект
const _this = this;
2022-12-03 12:05:28 +07:00
// Инициализация цели для переноса
const target = body ? document.body : shell;
2022-11-01 06:19:17 +07:00
// Перемещение камеры
if (camera === true) {
2022-12-03 12:05:28 +07:00
target.onmousedown = function (onmousedown) {
2022-11-01 06:19:17 +07:00
// Начало переноса
if (_this.#camera) {
// Разрешено двигать камеру (оболочку)
2022-12-03 12:05:28 +07:00
// Запись иконки курсора
target.style.cursor = 'move';
2022-11-01 06:19:17 +07:00
// Инициализация координат
const coords = _this.shell.getBoundingClientRect();
2022-12-03 12:05:28 +07:00
const x = onmousedown.pageX - coords.left + scrollX;
const y = onmousedown.pageY - coords.top + scrollY;
2022-12-03 12:29:29 +07:00
// Инициализация HTML-элемента-оболочки соединений
const connections = document.getElementById(_this.#id + '_connections');
2022-11-01 06:19:17 +07:00
2022-11-22 05:40:30 +07:00
// Инициализация функции переноса полотна
function move(onmousemove) {
2022-12-03 12:29:29 +07:00
// Инициализация буфера
let buffer;
// Запись нового отступа от лева для HTML-элемента оболочки графика
_this.shell.style.left = (buffer = onmousemove.pageX - x) + 'px';
// Запись нового отступа от лева для HTML-элемента оболочки соединений
connections.style.left = -buffer + 'px';
2022-11-22 05:40:30 +07:00
2022-12-03 12:29:29 +07:00
// Запись аттрибута с координатами для HTML-элемента оболочки соединений
connections.setAttribute('data-x', -buffer);
2022-12-03 12:05:28 +07:00
2022-12-03 12:29:29 +07:00
// Запись нового отступа от верха для HTML-элемента оболочки графика
_this.shell.style.top = (buffer = onmousemove.pageY - y) + 'px';
2022-12-03 12:05:28 +07:00
2022-12-03 12:29:29 +07:00
// Запись нового отступа от верха для HTML-элемента оболочки соединений
connections.style.top = -buffer + 'px';
2022-12-03 12:05:28 +07:00
2022-12-03 12:29:29 +07:00
// Запись аттрибута с координатами для HTML-элемента оболочки соединений
connections.setAttribute('data-y', -buffer);
for (const connection of _this.connections) {
// Перебор соединений
// Синхронизация
connection.synchronize(connection.from);
connection.synchronize(connection.to);
}
2022-11-01 06:19:17 +07:00
}
2022-11-22 05:40:30 +07:00
// Запись слушателя события: "перенос полотна"
2022-12-03 12:05:28 +07:00
target.onmousemove = move;
2022-11-01 06:19:17 +07:00
}
// Конец переноса
2022-12-03 12:05:28 +07:00
target.onmouseup = function () {
target.onmousemove = null;
target.onmouseup = null;
// Запись иконки курсора
target.style.cursor = null;
2022-11-01 06:19:17 +07:00
};
};
// Блокировка событий браузера (чтобы не дёргалось)
2022-12-03 12:05:28 +07:00
target.ondragstart = null;
2022-11-01 06:19:17 +07:00
}
}
write = function (data = {}) {
2022-12-03 12:29:29 +07:00
if (typeof data === 'object') {
2022-11-01 06:19:17 +07:00
// Получен обязательный входной параметр в правильном типе
// Инициализация узла
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) {
// Разрешено перемещать узлы
// Инициализация переноса узла
2022-11-01 06:19:17 +07:00
node.element.onmousedown = function (onmousedown) {
// Начало переноса
2022-11-26 22:41:43 +07:00
// Инициализация буфера позиционирования
const z = node.element.style.zIndex;
2022-11-01 06:19:17 +07:00
// Позиционирование над остальными узлами
node.element.style.zIndex = 5000;
2022-11-01 06:19:17 +07:00
// Инициализация буферов значения количества столкновений, притягиваний и отталкиваний
let collision, pushing, pulling;
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 05:40:30 +07:00
node.move(
2022-11-01 06:19:17 +07:00
onmousemove.pageX -
(onmousedown.pageX - n.left + s.left + scrollX),
2022-11-22 05:40:30 +07:00
onmousemove.pageY -
(onmousedown.pageY - n.top + s.top + scrollY),
_this.actions.collision,
_this.actions.pushing,
_this.actions.pulling,
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-26 22:41:43 +07:00
// Возвращение позиционирования
node.element.style.zIndex = z;
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(
2022-12-03 12:29:29 +07:00
new CustomEvent('graph.loaded', {
2022-11-22 05:40:30 +07:00
detail: { graph }
})
);