Transition from CodePen
This commit is contained in:
parent
4144a13a81
commit
7cd23a51da
|
@ -0,0 +1,292 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
*/
|
||||||
|
class graph {
|
||||||
|
// Оболочка (instanceof HTMLElement)
|
||||||
|
shell = document.getElementById("graph");
|
||||||
|
|
||||||
|
// Реестр узлов
|
||||||
|
nodes = new Set();
|
||||||
|
|
||||||
|
// Реестр соединений
|
||||||
|
connections = new Set();
|
||||||
|
|
||||||
|
// Класс узла
|
||||||
|
node = class node {
|
||||||
|
// Реестр входящих соединений
|
||||||
|
inputs = new Map();
|
||||||
|
|
||||||
|
// Реестр исходящих соединений
|
||||||
|
outputs = new Map();
|
||||||
|
|
||||||
|
// HTML-элемент
|
||||||
|
element;
|
||||||
|
|
||||||
|
// Параметры генерации
|
||||||
|
#diameter = 100;
|
||||||
|
#increase = 5;
|
||||||
|
|
||||||
|
constructor(data, graph) {
|
||||||
|
// Инициализация оболочки
|
||||||
|
const article = document.createElement("article");
|
||||||
|
article.id = graph.nodes.size;
|
||||||
|
article.classList.add("node", "unselectable");
|
||||||
|
article.style.top =
|
||||||
|
graph.shell.offsetHeight / 2 -
|
||||||
|
this.#diameter / 2 +
|
||||||
|
(0.5 - Math.random()) * 500 +
|
||||||
|
"px";
|
||||||
|
article.style.left =
|
||||||
|
graph.shell.offsetWidth / 2 -
|
||||||
|
this.#diameter / 2 +
|
||||||
|
(0.5 - Math.random()) * 500 +
|
||||||
|
"px";
|
||||||
|
|
||||||
|
if (typeof data.title === "string") {
|
||||||
|
// Найден заголовок узла
|
||||||
|
|
||||||
|
// Инициализация заголовка
|
||||||
|
const title = document.createElement("h4");
|
||||||
|
title.innerText = data.title;
|
||||||
|
|
||||||
|
// Запись в оболочку
|
||||||
|
article.appendChild(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запись в свойство
|
||||||
|
this.element = article;
|
||||||
|
|
||||||
|
// Запись в документ
|
||||||
|
graph.shell.appendChild(article);
|
||||||
|
|
||||||
|
// Инициализация
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
// Инициализация размера
|
||||||
|
this.element.style.width = this.element.style.height =
|
||||||
|
this.#diameter + this.#increase * this.inputs.size + "px";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#move = true;
|
||||||
|
#camera = true;
|
||||||
|
|
||||||
|
constructor(shell, camera = true) {
|
||||||
|
// Запись оболочки
|
||||||
|
if (shell instanceof HTMLElement) this.shell = shell;
|
||||||
|
|
||||||
|
// Инициализация ссылки на обрабатываемый объект
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Инициализация функции переноса
|
||||||
|
function move(e) {
|
||||||
|
// Запись нового отступа от верха
|
||||||
|
_this.shell.style.top = e.pageY - y + "px";
|
||||||
|
|
||||||
|
// Запись нового отступа от лева
|
||||||
|
_this.shell.style.left = e.pageX - x + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Перенос
|
||||||
|
document.onmousemove = move;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Конец переноса
|
||||||
|
_this.shell.onmouseup = function () {
|
||||||
|
document.onmousemove = null;
|
||||||
|
_this.shell.onmouseup = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Перещапись событий браузера (чтобы не дёргалось)
|
||||||
|
_this.shell.ondragstart = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write = function (data = {}) {
|
||||||
|
if (typeof data === "object") {
|
||||||
|
// Получен обязательный входной параметр в правильном типе
|
||||||
|
|
||||||
|
// Инициализация узла
|
||||||
|
const node = new this.node(data, this);
|
||||||
|
|
||||||
|
// Инициализация ссылки на обрабатываемый объект
|
||||||
|
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) {
|
||||||
|
// Начало переноса
|
||||||
|
|
||||||
|
// Позиционирование над остальными узлами
|
||||||
|
node.element.style.zIndex = 550;
|
||||||
|
|
||||||
|
if (!_this.#camera) {
|
||||||
|
// Запрещено двигать камеру (оболочку)
|
||||||
|
|
||||||
|
// Инициализация координат
|
||||||
|
const n = node.element.getBoundingClientRect();
|
||||||
|
const s = _this.shell.getBoundingClientRect();
|
||||||
|
|
||||||
|
// Инициализация функции переноса
|
||||||
|
function move(onmousemove) {
|
||||||
|
// Запись нового отступа от верха
|
||||||
|
node.element.style.top =
|
||||||
|
onmousemove.pageY -
|
||||||
|
(onmousedown.pageY - n.top + s.top + pageYOffset) +
|
||||||
|
"px";
|
||||||
|
|
||||||
|
// Запись нового отступа от лева
|
||||||
|
node.element.style.left =
|
||||||
|
onmousemove.pageX -
|
||||||
|
(onmousedown.pageX - n.left + s.left + pageXOffset) +
|
||||||
|
"px";
|
||||||
|
|
||||||
|
for (const [connection, target] of node.outputs) {
|
||||||
|
// Перебор исходящих соединений
|
||||||
|
|
||||||
|
// Инициализация координат для линии
|
||||||
|
const x1 = parseInt(node.element.style.left);
|
||||||
|
const y1 = parseInt(node.element.style.top);
|
||||||
|
|
||||||
|
// Запись новой координаты по горизонтали
|
||||||
|
connection.children[0].setAttribute(
|
||||||
|
"x1",
|
||||||
|
(isNaN(x1) ? 0 : x1) + node.element.offsetWidth / 2
|
||||||
|
);
|
||||||
|
|
||||||
|
// Запись новой координаты по вертикали
|
||||||
|
connection.children[0].setAttribute(
|
||||||
|
"y1",
|
||||||
|
(isNaN(y1) ? 0 : y1) + node.element.offsetHeight / 2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [connection, target] of node.inputs) {
|
||||||
|
// Перебор входящих соединений
|
||||||
|
|
||||||
|
// Инициализация координат для линии
|
||||||
|
const x2 = parseInt(node.element.style.left);
|
||||||
|
const y2 = parseInt(node.element.style.top);
|
||||||
|
|
||||||
|
// Запись новой координаты по горизонтали
|
||||||
|
connection.children[0].setAttribute(
|
||||||
|
"x2",
|
||||||
|
(isNaN(x2) ? 0 : x2) + node.element.offsetWidth / 2
|
||||||
|
);
|
||||||
|
|
||||||
|
// Запись новой координаты по вертикали
|
||||||
|
connection.children[0].setAttribute(
|
||||||
|
"y2",
|
||||||
|
(isNaN(y2) ? 0 : y2) + node.element.offsetHeight / 2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Перенос
|
||||||
|
document.onmousemove = move;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Конец переноса
|
||||||
|
node.element.onmouseup = function () {
|
||||||
|
// Очистка обработчиков событий
|
||||||
|
document.onmousemove = null;
|
||||||
|
node.element.onmouseup = null;
|
||||||
|
|
||||||
|
// Позиционирование вместе остальными узлами
|
||||||
|
node.element.style.zIndex = 500;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Перещапись событий браузера (чтобы не дёргалось)
|
||||||
|
node.element.ondragstart = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запись в реестр
|
||||||
|
this.nodes.add(node);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
connect = function (from, to) {
|
||||||
|
if (from instanceof this.node && to instanceof this.node) {
|
||||||
|
// Получены обязательные входные параметры в правильном типе
|
||||||
|
|
||||||
|
// Инициализация оболочки
|
||||||
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||||
|
svg.id = this.connections.size;
|
||||||
|
svg.classList.add("connection");
|
||||||
|
svg.setAttribute("data-from", from.element.id);
|
||||||
|
svg.setAttribute("data-to", to.element.id);
|
||||||
|
|
||||||
|
// Инициализация координат для линии
|
||||||
|
let x1 = parseInt(from.element.style.left);
|
||||||
|
x1 = isNaN(x1) ? 0 : x1;
|
||||||
|
let y1 = parseInt(from.element.style.top);
|
||||||
|
y1 = isNaN(y1) ? 0 : y1;
|
||||||
|
let x2 = parseInt(to.element.style.left);
|
||||||
|
x2 = isNaN(x2) ? 0 : x2;
|
||||||
|
let y2 = parseInt(to.element.style.top);
|
||||||
|
y2 = isNaN(y2) ? 0 : y2;
|
||||||
|
|
||||||
|
// Инициализация оболочки
|
||||||
|
const line = document.createElementNS(
|
||||||
|
"http://www.w3.org/2000/svg",
|
||||||
|
"line"
|
||||||
|
);
|
||||||
|
line.setAttribute("x1", x1 + from.element.offsetWidth / 2);
|
||||||
|
line.setAttribute("y1", y1 + from.element.offsetHeight / 2);
|
||||||
|
line.setAttribute("x2", x2 + to.element.offsetWidth / 2);
|
||||||
|
line.setAttribute("y2", y2 + to.element.offsetHeight / 2);
|
||||||
|
line.setAttribute("stroke", "grey");
|
||||||
|
line.setAttribute("stroke-width", "8px");
|
||||||
|
|
||||||
|
// Запись в оболочку
|
||||||
|
svg.append(line);
|
||||||
|
|
||||||
|
// Запись в документ
|
||||||
|
this.shell.appendChild(svg);
|
||||||
|
|
||||||
|
// Запись соединений в реестры узлов
|
||||||
|
from.outputs.set(svg, to);
|
||||||
|
to.inputs.set(svg, from);
|
||||||
|
|
||||||
|
// Запись в реестр ядра
|
||||||
|
this.connections.add([from, to]);
|
||||||
|
|
||||||
|
// Реинициализация узла-получателя
|
||||||
|
to.init();
|
||||||
|
|
||||||
|
return svg;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue