added repulsion

This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich 2022-11-22 21:31:26 +10:00
parent 8e5fcbeefa
commit e0ba73702d

386
graph.js
View File

@ -8,24 +8,18 @@ import victor from "https://cdn.skypack.dev/victor@1.1.0";
class graph { class graph {
// Оболочка (instanceof HTMLElement) // Оболочка (instanceof HTMLElement)
#shell = document.getElementById("graph"); #shell = document.getElementById("graph");
// Оболочка (instanceof HTMLElement)
get shell() { get shell() {
return this.#shell; return this.#shell;
} }
// Реестр узлов // Реестр узлов
#nodes = new Set(); #nodes = new Set();
// Реестр узлов
get nodes() { get nodes() {
return this.#nodes; return this.#nodes;
} }
// Реестр соединений // Реестр соединений
#connections = new Set(); #connections = new Set();
// Реестр соединений
get connections() { get connections() {
return this.#connections; return this.#connections;
} }
@ -34,79 +28,70 @@ class graph {
#node = class node { #node = class node {
// Реестр входящих соединений // Реестр входящих соединений
#inputs = new Set(); #inputs = new Set();
// Реестр входящих соединений
get inputs() { get inputs() {
return this.#inputs; return this.#inputs;
} }
// Реестр исходящих соединений // Реестр исходящих соединений
#outputs = new Set(); #outputs = new Set();
// Реестр исходящих соединений
get outputs() { get outputs() {
return this.#outputs; return this.#outputs;
} }
// Оператор // Оператор
#operator; #operator;
// Оператор
get operator() { get operator() {
return this.#operator; return this.#operator;
} }
// HTML-элемент // HTML-элемент
#element; #element;
// HTML-элемент
get element() { get element() {
return this.#element; return this.#element;
} }
// Наблюдатель // Наблюдатель
#observer = null; #observer = null;
// Наблюдатель
get observer() { get observer() {
return this.#observer; return this.#observer;
} }
// Реестр запрещённых к изменению параметров // Реестр запрещённых к изменению параметров
#block = new Set(["events"]); #block = new Set(["events"]);
// Реестр запрещённых к изменению параметров
get block() { get block() {
return this.#block; return this.#block;
} }
// Параметры генерации // Диаметр узла
#diameter = 100; #diameter = 100;
#increase = 5;
// Параметры генерации
get diameter() { get diameter() {
return this.#diameter; return this.#diameter;
} }
// Степень увеличения диаметра
#increase = 0;
get increase() { get increase() {
return this.#increase; return this.#increase;
} }
// Величина степени увеличения диаметра
#addition = 12;
get addition() {
return this.#addition;
}
// Обработка событий
actions = {
collision: true,
repulsion: true
};
constructor(operator, data) { constructor(operator, data) {
// Инициализация оболочки // Инициализация HTML-элемента узла
const article = document.createElement("article"); const a = document.createElement("a");
article.id = operator.nodes.size; a.id = operator.nodes.size;
article.classList.add("node", "unselectable"); a.classList.add("node", "unselectable");
article.style.top = if (typeof data.href === "string") a.href = data.href;
operator.shell.offsetHeight / 2 -
this.#diameter / 2 +
(0.5 - Math.random()) * 500 +
"px";
article.style.left =
operator.shell.offsetWidth / 2 -
this.#diameter / 2 +
(0.5 - Math.random()) * 500 +
"px";
if (typeof data.title === "string") { if (typeof data.title === "string") {
// Найден заголовок узла // Найден заголовок узла
@ -116,27 +101,43 @@ class graph {
title.innerText = data.title; title.innerText = data.title;
// Запись в оболочку // Запись в оболочку
article.appendChild(title); a.appendChild(title);
} }
// Запись в документ
operator.shell.appendChild(a);
// Запись в свойство // Запись в свойство
this.#element = article; this.#element = a;
// Запись в свойство // Запись в свойство
this.#operator = operator; this.#operator = operator;
// Запись в документ
operator.shell.appendChild(article);
// Инициализация // Инициализация
this.init(); this.init();
// Перемещение
this.move(
operator.shell.offsetWidth / 2 -
this.#diameter / 2 +
(0.5 - Math.random()) * 500,
operator.shell.offsetHeight / 2 -
this.#diameter / 2 +
(0.5 - Math.random()) * 500,
true,
true
);
} }
init(increase = 0) { init(increase = 0) {
// Изменение диаметра // Запись в свойство
if (increase !== 0) this.#diameter += this.#increase * increase; this.#increase = increase;
// Инициализация размера элемента // Инициализация диаметра
if (this.#increase !== 0)
this.#diameter += this.#addition ** this.#increase;
// Инициализация размера HTML-элемента
this.element.style.width = this.element.style.height = this.element.style.width = this.element.style.height =
this.#diameter + "px"; this.#diameter + "px";
@ -147,6 +148,8 @@ class graph {
this.#observer = new MutationObserver(function (mutations) { this.#observer = new MutationObserver(function (mutations) {
for (const mutation of mutations) { for (const mutation of mutations) {
if (mutation.type === "attributes") { if (mutation.type === "attributes") {
// Перехвачено изменение аттрибута
// Запись параметра в инстанцию бегущей строки // Запись параметра в инстанцию бегущей строки
_this.configure(mutation.attributeName); _this.configure(mutation.attributeName);
} }
@ -160,6 +163,191 @@ class graph {
}); });
} }
move(x, y, collision = true, repulsion = false) {
// Запись отступов
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);
// Синхронизация местоположения исходящих и входящих соединений
for (const connection of this.outputs) connection.sync(this);
// Синхронизация местоположения входящих соединений
for (const connection of this.inputs) connection.sync(this);
// Обработка столкновений
if (collision) this.collision(this.operator.nodes);
// Обработка отталкивания
if (repulsion) this.repulsion(this.operator.nodes);
}
collision(nodes) {
// Инициализация буфера реестра узлов
const registry = new Set(nodes);
// Удаление текущего узла из буфера
registry.delete(this);
// Обработка коллизии
for (const node of registry) {
// Перебор узлов в реестре
// Инициализация вектора между узлами
let between;
// Инициализация ускорения
let increase = 0;
// Инициализация максимального количества итераций
let iterations = 300;
do {
// Произошла коллизия (границы кругов перекрылись)
// Инициализация универсального буфера
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);
// Проверка на столкновение узлов
if (
!node.actions.collision ||
between.length() > node.diameter / 2 + this.diameter / 2 ||
--iterations <= 0
)
break;
// Инициализация координат вектора (узла с которым произошло столкновение)
let vector = new victor(x1, y1)
.add(new victor(between.x, between.y).norm().unfloat())
.subtract(
new victor(
node.element.offsetWidth / 2,
node.element.offsetHeight / 2
)
);
// Перемещение узла с которым произошло столкновение
if (node.actions.collision) node.move(vector.x, vector.y, true, true);
// Проверка на столкновение узлов
} while (
node.actions.collision &&
between.length() <= node.diameter / 2 + this.diameter / 2
);
}
}
repulsion(nodes) {
// Инициализация буфера реестра узлов
const registry = new Set(nodes);
// Удаление текущего узла из буфера
registry.delete(this);
// Инициализация ссылки на ядро
const _this = this;
// Обработка отталкивания
for (const node of registry) {
// Перебор узлов в буфере реестра
// Инициализация вектора между узлами
let between;
// Минимальная дистанция между узлами
const distance = 100;
// Инициализация максимального количества итераций
let iterations = 300;
function move() {
// Инициализация универсального буфера
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);
// Проверка на столкновение узлов
if (
!node.actions.repulsion ||
between.length() >
node.diameter / 2 +
_this.diameter / 2 +
distance +
(_this.diameter / 4) ** _this.increase ||
--iterations <= 0
)
return;
// Инициализация координат вектора (узла с которым произошло столкновение)
let vector = new victor(x1, y1)
.add(new victor(between.x, between.y).norm().unfloat())
.subtract(
new victor(
node.element.offsetWidth / 2,
node.element.offsetHeight / 2
)
);
// Перемещение узла с которым произошло столкновение
if (node.actions.repulsion) node.move(vector.x, vector.y, true, true);
// Проверка расстояния
if (
node.actions.repulsion &&
between.length() <=
node.diameter / 2 +
_this.diameter / 2 +
distance +
(_this.diameter / 4) ** (_this.increase ** _this.increase)
)
setTimeout(move, between.length() / 100);
}
if (node.actions.repulsion) move();
}
}
configure(attribute) { configure(attribute) {
// Инициализация названия параметра // Инициализация названия параметра
const parameter = (/^data-graph-(\w+)$/.exec(attribute) ?? [, null])[1]; const parameter = (/^data-graph-(\w+)$/.exec(attribute) ?? [, null])[1];
@ -195,91 +383,6 @@ class graph {
: buffer; : buffer;
} }
} }
return this;
}
move(x, y) {
// Запись отступов
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);
// Перемещение исходящих соединений до узла
for (const connection of this.outputs) connection.move(this);
// Перемещение входящих соединений до узла
for (const connection of this.inputs) connection.move(this);
// Обработка столкновений
this.collision(this.operator.nodes);
}
collision(nodes) {
// Инициализация буфера реестра узлов
const buffer = new Set(nodes);
// Удаление текущего узла из буфера
buffer.delete(this);
// Удаление текущего узла из буфера
buffer.delete(this);
// Обработка коллизии
for (const node of buffer) {
// Перебор узлов в реестре
// Инициализация вектора между узлами
let between;
// Инициализация ускорения
let increase = 0;
// Инициализация количества итераций
let iterations = 300;
do {
// Произошла коллизия (границы кругов перекрылись)
// Инициализация координат целевого узла
let x1 = parseInt(node.element.style.left);
x1 = (isNaN(x1) ? 0 : x1) + node.element.offsetWidth / 2;
let y1 = parseInt(node.element.style.top);
y1 = (isNaN(y1) ? 0 : y1) + node.element.offsetHeight / 2;
// Инициализация координат обрабатываемого узла
let x2 = parseInt(this.element.style.left);
x2 = (isNaN(x2) ? 0 : x2) + this.element.offsetWidth / 2;
let y2 = parseInt(this.element.style.top);
y2 = (isNaN(y2) ? 0 : y2) + this.element.offsetHeight / 2;
// Реинициализация вектора между узлами
between = new victor(x1 - x2, y1 - y2);
// Проверка на столкновение узлов
if (
between.length() > node.diameter / 2 + this.diameter / 2 ||
--iterations === 0
)
break;
// Инициализация координат вектора (узла с которым произошло столкновение)
let vector = new victor(x1, y1)
.add(new victor(between.x, between.y).norm().unfloat())
.subtract(
new victor(
node.element.offsetWidth / 2,
node.element.offsetHeight / 2
)
);
// Перемещение узла с которым произошло столкновение
node.move(vector.x, vector.y);
} while (between.length() <= node.diameter / 2 + this.diameter / 2);
}
} }
}; };
@ -381,26 +484,27 @@ class graph {
} }
/** /**
* Переместить * Синхронизировать местоположение со связанным узлом
* *
* @param {node} node Инстанция узла (связанного с соединением) * @param {node} node Инстанция узла (связанного с соединением)
*/ */
move(node) { sync(node) {
// Инициализация названий аттрибутов // Инициализация названий аттрибутов
let x, y; let x = "x",
y = "y";
if (node === this.from) { if (node === this.from) {
// Исходящее соединение // Исходящее соединение
// Запись названий аттрибутов // Запись названий аттрибутов
x = "x1"; x += 1;
y = "y1"; y += 1;
} else if (node === this.to) { } else if (node === this.to) {
// Входящее соединение // Входящее соединение
// Запись названий аттрибутов // Запись названий аттрибутов
x = "x2"; x += 2;
y = "y2"; y += 2;
} else return; } else return;
// Инициализация универсального буфера // Инициализация универсального буфера
@ -513,12 +617,17 @@ class graph {
// Инициализация функции переноса узла // Инициализация функции переноса узла
function move(onmousemove) { function move(onmousemove) {
// Запись обработки столкновений и отталкивания
node.actions.collision = node.actions.repulsion = false;
// Перемещение // Перемещение
node.move( node.move(
onmousemove.pageX - onmousemove.pageX -
(onmousedown.pageX - n.left + s.left + pageXOffset), (onmousedown.pageX - n.left + s.left + pageXOffset),
onmousemove.pageY - onmousemove.pageY -
(onmousedown.pageY - n.top + s.top + pageYOffset) (onmousedown.pageY - n.top + s.top + pageYOffset),
true,
true
); );
} }
@ -532,6 +641,9 @@ class graph {
document.onmousemove = null; document.onmousemove = null;
node.element.onmouseup = null; node.element.onmouseup = null;
// Запись обработки столкновений и отталкивания
node.actions.collision = node.actions.repulsion = true;
// Позиционирование вместе остальными узлами // Позиционирование вместе остальными узлами
node.element.style.zIndex = 500; node.element.style.zIndex = 500;
}; };