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 += "