diff --git a/graph.js b/graph.js new file mode 100644 index 0000000..e91b348 --- /dev/null +++ b/graph.js @@ -0,0 +1,292 @@ +"use strict"; + +/** + * @author Arsen Mirzaev Tatyano-Muradovich + */ +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; + } + }; +}