From deeff21d99489c34a82bf36701c9718a1c8cc015 Mon Sep 17 00:00:00 2001 From: ExtReMLapin <3909752+ExtReMLapin@users.noreply.github.com> Date: Thu, 13 Feb 2025 11:30:45 +0100 Subject: [PATCH] Fix for #1979 part 2 --- .../static/js/studio-graph-widget.js | 967 ++---------------- 1 file changed, 87 insertions(+), 880 deletions(-) diff --git a/studio/src/main/resources/static/js/studio-graph-widget.js b/studio/src/main/resources/static/js/studio-graph-widget.js index 0efa152f8..2e949e9f8 100644 --- a/studio/src/main/resources/static/js/studio-graph-widget.js +++ b/studio/src/main/resources/static/js/studio-graph-widget.js @@ -1,922 +1,129 @@ -var globalRenderedVerticesRID = {}; -var globalTotalEdges = 0; -var globalSelected = null; +function cutSelection() { + graphOperationInBatch(function () { + let selected = globalCy.elements(":selected"); + if (selected.length == 0) return; -function renderGraph() { - if (globalResultset == null) return; - - globalCy = null; - - $("#graphSpacing").val(globalGraphSettings.graphSpacing); - - let elements = []; - globalRenderedVerticesRID = {}; - - globalTotalEdges = 0; - - for (let i in globalResultset.vertices) { - let vertex = globalResultset.vertices[i]; - assignVertexColor(vertex.t); - assignProperties(vertex); - } - - for (let i in globalResultset.edges) { - let edge = globalResultset.edges[i]; - assignProperties(edge); - } - - let reachedMax = false; - for (let i in globalResultset.vertices) { - let vertex = globalResultset.vertices[i]; - - let rid = vertex["r"]; - if (rid == null) continue; - - let v = { data: createVertex(vertex), classes: vertex["t"] }; - elements.push(v); - - globalRenderedVerticesRID[rid] = true; - - if (elements.length >= globalGraphMaxResult) { - reachedMax = true; - break; - } - } - - for (let i in globalResultset.edges) { - let edge = globalResultset.edges[i]; - if (globalRenderedVerticesRID[edge.i] && globalRenderedVerticesRID[edge.o]) { - // DISPLAY ONLY EDGES RELATIVE TO VERTICES THAT ARE PART OF THE GRAPH - elements.push({ data: createEdge(edge), classes: edge["t"] }); - ++globalTotalEdges; - } - } - - globalLayout = { - name: "cola", - animate: true, - refresh: 2, - ungrabifyWhileSimulating: true, - nodeSpacing: function (node) { - return globalGraphSettings.graphSpacing; - }, - spacingFactor: 1.75, - }; - - let styles = [ - { - selector: "node", - style: { - label: "data(label)", - width: "data(size)", - height: "data(size)", - "border-color": "gray", - "border-width": 0, - "text-valign": "center", - "text-halign": "center", - "text-wrap": "wrap", - "text-max-width": 100, - "z-index-compare": "manual", - "z-index": 2, - }, - }, - { - selector: "edge", - style: { - width: 1, - label: "data(label)", - color: "gray", - "line-color": "gray", - "target-arrow-color": "gray", - "target-arrow-shape": "triangle", - "curve-style": "bezier", - "edge-text-rotation": "autorotate", - "text-outline-color": "#F7F7F7", - "text-outline-width": 8, - "z-index-compare": "manual", - "z-index": 1, - }, - }, - ]; - - assignStyles(styles); - - // ADD SELECTED STYLES TO GET PRIORITY OVER OTHER STYLES - styles.push({ - selector: "node:selected", - style: { - color: "red", - "border-color": "red", - "border-width": 10, - "z-index-compare": "manual", - "z-index": 3, - }, - }); - - styles.push({ - selector: "edge:selected", - style: { - color: "red", - "line-color": "red", - width: 10, - "z-index-compare": "manual", - "z-index": 3, - }, - }); - - globalCy = cytoscape({ - container: $("#graph"), - elements: elements, - style: styles, + removeGraphElement(selected); + globalCy.makeLayout(globalLayout).run(); }); - - initGraph(); - - let warning = null; - if (reachedMax) { - warning = "Returned more than " + globalGraphMaxResult + " items, partial results will be returned. Consider setting a limit in the query."; - globalNotify("Warning", warning, "warning"); - } - - updateGraphStatus(warning); } -function initGraph() { - setGraphStyles(); +function cropSelection() { + graphOperationInBatch(function () { + let selected = globalCy.nodes().not(globalCy.nodes(":selected")); + if (selected.length == 0) return; - globalCy.cxtmenu({ - selector: "node", - menuRadius: function (ele) { - return 50; - }, - adaptativeNodeSpotlightRadius: true, - openMenuEvents: "taphold", - commands: [ - { - content: '', - select: function (ele) { - removeGraphElement(ele); - }, - }, - { - content: '', - select: function (ele) { - loadNodeNeighbors("both", ele.data("id")); - }, - }, - { - content: '', - select: function (ele) { - loadNodeNeighbors("out", ele.data("id")); - }, - }, - { - content: '', - select: function (ele) { - loadNodeNeighbors("in", ele.data("id")); - }, - }, - ], + removeGraphElement(selected); + globalCy.makeLayout(globalLayout).run(); }); - - globalCy.cxtmenu({ - selector: "edge", - adaptativeNodeSpotlightRadius: false, - commands: [ - { - content: '', - select: function (ele) { - removeGraphElement(ele); - }, - }, - ], - }); - - globalCy.on("select", "node", function (event) { - displaySelectedNode(); - }); - - globalCy.on("select", "edge", function (event) { - displaySelectedEdge(); - }); - - globalCy.makeLayout(globalLayout).run(); -} - -function createVertex(vertex) { - let type = vertex["t"]; - - getOrCreateStyleTypeAttrib(type, "element", "v"); - - let label = "@type"; - if (getOrCreateStyleTypeAttrib(type, "labelText") != null) label = getOrCreateStyleTypeAttrib(type, "labelText"); - if (label == "@type") label = type; - else label = vertex["p"][label]; - - if (label == null) label = ""; - - return { id: vertex["r"], label: label, size: 70 + 2 * label.length, type: type, weight: vertex["i"] + vertex["o"], properties: vertex["p"] }; -} - -function createEdge(edge) { - let type = edge["t"]; - - getOrCreateStyleTypeAttrib(type, "element", "e"); - - let label = "@type"; - if (getOrCreateStyleTypeAttrib(type, "labelText") != null) label = getOrCreateStyleTypeAttrib(type, "labelText"); - if (label == "@type") label = type; - else label = edge["p"][label]; - - if (label == null) label = ""; - - return { id: edge["r"], label: label, type: type, source: edge["o"], target: edge["i"], properties: edge["p"] }; -} - -function assignVertexColor(type) { - let color = getOrCreateStyleTypeAttrib(type, "shapeColor"); - if (color == null) { - if (globalLastColorIndex >= globalBgColors.length) globalLastColorIndex = 0; - getOrCreateStyleTypeAttrib(type, "labelColor", globalFgColors[globalLastColorIndex]); - getOrCreateStyleTypeAttrib(type, "shapeColor", globalBgColors[globalLastColorIndex]); - ++globalLastColorIndex; - } } -function assignProperties(element) { - let type = element["t"]; - let properties = globalGraphPropertiesPerType[type]; - if (properties == null) { - properties = {}; - globalGraphPropertiesPerType[type] = properties; - } - - for (let p in element.p) properties[p] = true; -} - -function assignStyles(styles) { - if (styles == null) styles = []; - - for (let type in globalGraphSettings.types) { - let element = getOrCreateStyleTypeAttrib(type, "element"); - - let labelColor = getOrCreateStyleTypeAttrib(type, "labelColor"); - if (labelColor == null) labelColor = "black"; - - let borderColor = getOrCreateStyleTypeAttrib(type, "borderColor"); - if (borderColor == null) borderColor = "gray"; - - let shapeColor = getOrCreateStyleTypeAttrib(type, "shapeColor"); - let icon = getOrCreateStyleTypeAttrib(type, "icon"); - - let shapeSize = getOrCreateStyleTypeAttrib(type, "shapeSize"); - if (shapeSize == null) shapeSize = element == "v" ? "data(size)" : 1; - - let labelSize = getOrCreateStyleTypeAttrib(type, "labelSize"); - if (labelSize == null) labelSize = 1; - - let style = { - selector: "." + type, - style: { - width: shapeSize, - height: shapeSize, - color: labelColor, - "background-color": shapeColor, - "font-size": labelSize + "em", - "z-index": element == "v" ? 2 : 1, - }, - }; +function searchInGraph() { + graphOperationInBatch(function () { + let text = $("#inputGraphSearch").val().trim(); + if (text == "") return; - if (element == "e") style.style["line-color"] = shapeColor; + let selected = globalCy.nodes(":selected"); - let labelPosition = getOrCreateStyleTypeAttrib(type, "labelPosition"); - let borderSize = getOrCreateStyleTypeAttrib(type, "borderSize"); + for (let i in globalCy.elements()) { + let el = globalCy.elements()[i]; + if (!el.data) continue; - if (icon != null) { - if (labelPosition == null) { - labelPosition = "bottom center"; - getOrCreateStyleTypeAttrib(type, "labelPosition", labelPosition); - } - labelPosition = labelPosition.split(" "); - - if (borderSize == null) borderSize = 0; + let data = el.data(); - style.style["background-opacity"] = 0; - style.style["text-max-width"] = 200; - } else { - if (labelPosition == null) labelPosition = "center center"; - labelPosition = labelPosition.split(" "); + if (text == data.id) el.select(); - if (borderSize == null) borderSize = 1; - - style.style["text-max-width"] = 100; - } + if (data.label != null && data.label.indexOf(text) > -1) el.select(); - style.style["border-color"] = borderColor; - style.style["border-width"] = borderSize; - style.style["text-valign"] = labelPosition[0]; - style.style["text-halign"] = labelPosition[1]; + if (data.type != null && data.type.indexOf(text) > -1) el.select(); - styles.push(style); - styles.push({ - selector: "." + type + ":selected", - style: { - "border-color": "red", - "border-width": 5, - }, - }); - } - - return styles; -} - -function setGraphStyles() { - let nodeHtmlStyles = []; - for (let type in globalGraphSettings.types) { - let iconColor = getOrCreateStyleTypeAttrib(type, "iconColor"); - if (iconColor == null) iconColor = "black"; - - let icon = getOrCreateStyleTypeAttrib(type, "icon"); - if (icon != null) { - let iconSize = getOrCreateStyleTypeAttrib(type, "iconSize"); - if (iconSize == null) iconSize = 2; - - let iconPosition = getOrCreateStyleTypeAttrib(type, "iconPosition"); - if (iconPosition == null) iconPosition = "center center"; - iconPosition = iconPosition.split(" "); - - nodeHtmlStyles.push({ - query: "." + type, - valign: iconPosition[0], - halign: iconPosition[1], - valignBox: "center", - tpl: function (data) { - return ""; - }, - }); - } - } - globalCy.nodeHtmlLabel(nodeHtmlStyles); -} - -function removeGraphElement(ele) { - if (ele == null) return; - - globalCy.remove(ele); - - let elements; - if (ele instanceof Array) elements = ele; - else { - elements = []; - elements.push(ele.data().id); - } - - try { - for (let i in elements) { - if (!elements[i].data) continue; - - let rid = elements[i].data().id; - - arrayRemoveAll(globalResultset.vertices, (row) => row.r == rid); - let edgeRemoved = arrayRemoveAll(globalResultset.edges, (row) => row.r == rid || row.i == rid || row.o == rid); - globalTotalEdges -= edgeRemoved.length; - delete globalRenderedVerticesRID[rid]; - } - } finally { - updateGraphStatus(); - } -} - -function loadNodeNeighbors(direction, rid) { - let database = escapeHtml($("#schemaInputDatabase").val()); - - $("#executeSpinner").show(); - - let beginTime = new Date(); - - jQuery - .ajax({ - type: "POST", - url: "api/v1/command/" + database, - data: JSON.stringify({ - language: "sql", - command: "select expand( " + direction + "E() ) from " + rid, - serializer: "studio", - }), - beforeSend: function (xhr) { - xhr.setRequestHeader("Authorization", globalCredentials); - }, - }) - .done(function (data) { - globalCy.startBatch(); - - let reachedMax = false; - for (let i in data.result.vertices) { - if (Object.keys(globalRenderedVerticesRID).length >= globalGraphMaxResult) { - reachedMax = true; + for (let prop in data.properties) { + let value = data.properties[prop]; + if (value != null && value.toString().indexOf(text) > -1) { + el.select(); break; } - - let vertex = data.result.vertices[i]; - - assignVertexColor(vertex.t); - assignProperties(vertex); - - globalResultset.vertices.push(vertex); - - globalCy.add([ - { - group: "nodes", - data: createVertex(vertex), - classes: vertex["t"], - }, - ]); - - globalRenderedVerticesRID[vertex.r] = true; } + } - for (let i in data.result.edges) { - let edge = data.result.edges[i]; - - if (!globalRenderedVerticesRID[edge.i] || !globalRenderedVerticesRID[edge.o]) continue; - - assignProperties(edge); - - globalResultset.edges.push(edge); - globalCy.add([ - { - group: "edges", - data: createEdge(edge), - classes: edge["t"], - }, - ]); - - ++globalTotalEdges; - } - - let typeStyles = assignStyles(); - for (i in typeStyles) { - let s = typeStyles[i]; - globalCy.style().selector(s.selector).style(s.style); - } - - setGraphStyles(); - - globalCy.makeLayout(globalLayout).run(); + if (!globalGraphSettings.cumulativeSelection) selected.unselect(); + }); +} - globalCy.endBatch(); +function selectGraphElementByType(type) { + graphOperationInBatch(function () { + let selected = globalCy.nodes(":selected"); - let warning = null; - if (reachedMax) { - warning = "Returned more than " + globalGraphMaxResult + " items, partial results will be returned. Consider setting a limit in the query."; - globalNotify("Warning", warning, "warning"); - } + globalCy.elements("[type = '" + type + "']").select(); - updateGraphStatus(warning); - }) - .fail(function (jqXHR, textStatus, errorThrown) { - globalNotify("Error", escapeHtml(jqXHR.responseText), "danger"); - }) - .always(function (data) { - $("#executeSpinner").hide(); - }); + if (!globalGraphSettings.cumulativeSelection) selected.unselect(); + }); } -function addNodeFromRecord(rid) { - if (globalResultset == null) globalResultset = {}; - if (globalResultset.vertices == null) globalResultset.vertices = []; - if (globalResultset.edges == null) globalResultset.edges = []; - if (globalResultset.records == null) globalResultset.records = []; - - let vertex = null; - for (let i in globalResultset.vertices) { - let v = globalResultset.vertices[i]; - if (v.r == rid) { - vertex = v; - break; - } - } +function selectOrphanVertices() { + graphOperationInBatch(function () { + if (!globalGraphSettings.cumulativeSelection) globalCy.nodes(":selected").unselect(); - if (vertex == null) { - // LOAD FROM THE DATABASE - jQuery - .ajax({ - type: "POST", - url: "api/v1/command/" + escapeHtml($("#schemaInputDatabase").val()), - async: false, - data: JSON.stringify({ - language: "sql", - command: "select from " + rid, - serializer: "graph", - }), - beforeSend: function (xhr) { - xhr.setRequestHeader("Authorization", globalCredentials); - }, + globalCy + .nodes() + .filter(function (ele) { + return ele.outgoers().length == 0 && ele.incomers().length == 0; }) - .done(function (data) { - vertex = data.result.vertices[0]; - globalResultset.vertices.push(vertex); - }); - } - - if (globalCy != null) globalCy.elements("node:selected").unselect(); - else renderGraph(); - - globalActivateTab("tab-graph"); - - let node = globalCy.nodes("[id = '" + rid + "']")[0]; - - node.select(); - - globalCy.makeLayout(globalLayout).run(); + .select(); + }); } -function getOrCreateStyleTypeAttrib(type, attrib, value) { - let style = globalGraphSettings.types[type]; - if (style == null) { - style = {}; - globalGraphSettings.types[type] = style; - } - - if (typeof value !== "undefined") style[attrib] = value; - - return style[attrib]; +function invertVertexSelection() { + graphOperationInBatch(function () { + let selected = globalCy.nodes(":selected"); + let notSelected = globalCy.nodes().not(":selected"); + selected.unselect(); + notSelected.select(); + }); } -function displaySelectedNode() { - if (!globalEnableElementPanel) return; +function selectNeighbors(depth) { + graphOperationInBatch(function () { + if (depth < 1) depth = 1; - globalSelected = globalCy.elements("node:selected"); - if (globalSelected.length < 1) { - $("#customToolbar").empty(); - $("#graphPropertiesTable").empty(); - $("#graphPropertiesType").html("Select an element to see its properties"); - return; - } - - let data = null; - if (globalSelected.length == 1) { - data = globalSelected[0].data(); - - let summary = " "; - summary += "
"; - summary += "
Properties
"; - - $("#graphPropertiesType").html(summary); + let selected = globalCy.nodes(":selected"); - let table = "NameValue"; - table += ""; + let out = selected.outgoers(); + let inc = selected.incomers(); - for (let p in data.properties) { - let value = data.properties[p]; - if (Array.isArray(value) || typeof value === "object") value = JSON.stringify(value); - table += "" + escapeHtml(p) + "" + escapeHtml(value) + ""; + for (let i = 1; i < depth; ++i) { + out = out.outgoers(); + inc = inc.incomers(); } + out.select(); + inc.select(); - $("#graphPropertiesTable").html(table); - } - - let selectedElementTypes = {}; - for (let i = 0; i < globalSelected.length; ++i) { - let type = globalSelected[i].data()["type"]; - selectedElementTypes[type] = true; - } - - let type = null; - if (Object.keys(selectedElementTypes).length == 1) { - type = globalSelected[0].data()["type"]; - let properties = globalGraphPropertiesPerType[type]; - - let labelText = getOrCreateStyleTypeAttrib(type, "labelText"); - if (labelText == null) labelText = "@type"; - - let labelColor = getOrCreateStyleTypeAttrib(type, "labelColor"); - if (labelColor == null) labelColor = "black"; - - let layout = "
Layout for type " + type; - layout += - "
"; - layout += "
"; - layout += "
"; - - // LABEL - layout += "
"; - layout += "
"; - layout += "
"; - - let labelPosition = getOrCreateStyleTypeAttrib(type, "labelPosition"); - if (labelPosition == null) labelPosition = "center center"; - - layout += - "
"; - - let labelSize = getOrCreateStyleTypeAttrib(type, "labelSize"); - if (labelSize == null) labelSize = 1; - - layout += - "
'; - - // ICON - let icon = getOrCreateStyleTypeAttrib(type, "icon"); - if (icon == null) icon = ""; - - let iconSize = getOrCreateStyleTypeAttrib(type, "iconSize"); - if (iconSize == null) iconSize = 2; - - let iconColor = getOrCreateStyleTypeAttrib(type, "iconColor"); - if (iconColor == null) iconColor = "black"; - - layout += "
"; - layout += "
"; - layout += - ""; - layout += ""; - layout += - "
'; - - layout += "
"; - - let iconPosition = getOrCreateStyleTypeAttrib(type, "iconPosition"); - if (iconPosition == null) iconPosition = "center center"; - - layout += - "
"; - - layout += - "
'; - - // BORDER - let borderSize = getOrCreateStyleTypeAttrib(type, "borderSize"); - if (borderSize == null) borderSize = "1"; - - let borderColor = getOrCreateStyleTypeAttrib(type, "borderColor"); - if (borderColor == null) borderColor = "gray"; - - layout += "
"; - layout += - "
'; - layout += "
"; - - // SHAPE - let shapeSize = getOrCreateStyleTypeAttrib(type, "shapeSize"); - if (shapeSize == null) shapeSize = "auto"; - - let shapeColor = getOrCreateStyleTypeAttrib(type, "shapeColor"); - if (shapeColor == null) shapeColor = null; - - layout += "
"; - - layout += - "
"; - layout += - "
'; - layout += "
"; - - layout += "
"; - - $("#graphLayout").html(layout); - $("#graphLabel").change(function () { - getOrCreateStyleTypeAttrib(type, "labelText", $("#graphLabel").val()); - renderGraph(); - }); - $("#graphIcon").iconpicker(); - $("#graphIcon").on("iconpickerSelected", function (event) { - getOrCreateStyleTypeAttrib(type, "icon", event.iconpickerValue); - renderGraph(); - }); - - $("#labelColor").colorselector({ - callback: function (value, color, title) { - getOrCreateStyleTypeAttrib(type, "labelColor", value); - renderGraph(); - }, - }); - - $("#iconColor").colorselector({ - callback: function (value, color, title) { - getOrCreateStyleTypeAttrib(type, "iconColor", value); - renderGraph(); - }, - }); - - $("#borderColor").colorselector({ - callback: function (value, color, title) { - getOrCreateStyleTypeAttrib(type, "borderColor", value); - renderGraph(); - }, - }); - - $("#shapeColor").colorselector({ - callback: function (value, color, title) { - getOrCreateStyleTypeAttrib(type, "shapeColor", value); - renderGraph(); - }, - }); - } - - if (globalSelected.length == 1) { - let actions = "
Actions
"; - actions += ""; - - $("#graphActions").html(actions); - } + if (!globalGraphSettings.cumulativeSelection) selected.unselect(); + }); } -function displaySelectedEdge() { - globalSelected = globalCy.elements("edge:selected"); - if (globalSelected.length < 1) { - $("#customToolbar").empty(); - $("#graphPropertiesTable").empty(); - $("#graphPropertiesType").html("Select an element to see its properties"); - return; - } - - let data = null; - if (globalSelected.length == 1) { - data = globalSelected[0].data(); - - let summary = " "; - summary += "
"; - summary += "
Properties
"; - - $("#graphPropertiesType").html(summary); - - let table = "NameValue"; - table += ""; - - for (let p in data.properties) { - let value = data.properties[p]; - if (Array.isArray(value) || typeof value === "object") value = JSON.stringify(value); - table += "" + escapeHtml(p) + "" + escapeHtml(value) + ""; +function shortestPath() { + graphOperationInBatch(function () { + let selected = globalCy.nodes(":selected"); + if (selected.length != 2) { + globalAlert("Select 2 nodes"); + return; } - table += ""; - - $("#graphPropertiesTable").html(table); - } - - let selectedElementTypes = {}; - for (let i = 0; i < globalSelected.length; ++i) { - let type = globalSelected[i].data()["type"]; - selectedElementTypes[type] = true; - } - - let type = null; - if (Object.keys(selectedElementTypes).length == 1) { - type = globalSelected[0].data()["type"]; - let properties = globalGraphPropertiesPerType[type]; - - let sel = getOrCreateStyleTypeAttrib(type, "labelText"); - if (sel == null) sel = "@type"; - - let layout = "
Layout for type " + type; - layout += - "
"; - - layout += "
"; - layout += "
"; - layout += "
"; - - // SHAPE - let shapeSize = getOrCreateStyleTypeAttrib(type, "shapeSize"); - if (shapeSize == null) shapeSize = 1; - - let shapeColor = getOrCreateStyleTypeAttrib(type, "shapeColor"); - if (shapeColor == null) shapeColor = "gray"; + var dijkstra = globalCy.elements().dijkstra(selected[0]); + var pathToJ = dijkstra.pathTo(selected[1]); - layout += "
"; - layout += - "
'; - layout += "
"; - - layout += "
"; - - $("#graphLayout").html(layout); - - $("#shapeColor").colorselector({ - callback: function (value, color, title) { - getOrCreateStyleTypeAttrib(type, "shapeColor", value); - renderGraph(); - }, - }); - - $("#graphLabel").change(function () { - getOrCreateStyleTypeAttrib(type, "labelText", $("#graphLabel").val()); - renderGraph(); - }); - } - - if (globalSelected.length == 1) { - let actions = "
Actions
"; - actions += ""; + pathToJ.select(); + }); +} - $("#graphActions").html(actions); +function graphOperationInBatch(callback) { + globalCy.startBatch(); + globalEnableElementPanel = false; + try { + callback(); + } finally { + globalEnableElementPanel = true; + globalCy.endBatch(); + displaySelectedNode(); } }