@matdata/yasgui-graph-plugin 1.4.2 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +227 -30
- package/dist/yasgui-graph-plugin.cjs.css +115 -0
- package/dist/yasgui-graph-plugin.cjs.css.map +2 -2
- package/dist/yasgui-graph-plugin.cjs.js +817 -28
- package/dist/yasgui-graph-plugin.cjs.js.map +4 -4
- package/dist/yasgui-graph-plugin.css +114 -1
- package/dist/yasgui-graph-plugin.esm.css +115 -0
- package/dist/yasgui-graph-plugin.esm.css.map +2 -2
- package/dist/yasgui-graph-plugin.esm.js +817 -28
- package/dist/yasgui-graph-plugin.esm.js.map +4 -4
- package/dist/yasgui-graph-plugin.min.css +1 -1
- package/dist/yasgui-graph-plugin.min.css.map +2 -2
- package/dist/yasgui-graph-plugin.min.js +24 -20
- package/dist/yasgui-graph-plugin.min.js.map +4 -4
- package/package.json +2 -2
|
@@ -41,14 +41,18 @@ function truncateLabel(text, maxLength = 50) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
// src/networkConfig.ts
|
|
44
|
-
function getDefaultNetworkOptions(themeColors) {
|
|
44
|
+
function getDefaultNetworkOptions(themeColors, settings) {
|
|
45
|
+
const curved = !settings || settings.edgeStyle !== "straight";
|
|
46
|
+
const nodeSizeMap = { small: 6, medium: 10, large: 16 };
|
|
47
|
+
const nodeSize = (settings == null ? void 0 : settings.nodeSize) ? nodeSizeMap[settings.nodeSize] : 10;
|
|
48
|
+
const showNodeLabels = (settings == null ? void 0 : settings.showNodeLabels) !== false;
|
|
45
49
|
return {
|
|
46
50
|
// Configure canvas background color based on theme
|
|
47
51
|
configure: {
|
|
48
52
|
enabled: false
|
|
49
53
|
},
|
|
50
54
|
physics: {
|
|
51
|
-
enabled:
|
|
55
|
+
enabled: (settings == null ? void 0 : settings.physicsEnabled) !== false,
|
|
52
56
|
stabilization: {
|
|
53
57
|
enabled: true,
|
|
54
58
|
iterations: 200,
|
|
@@ -68,14 +72,16 @@ function getDefaultNetworkOptions(themeColors) {
|
|
|
68
72
|
dragView: true,
|
|
69
73
|
zoomView: true,
|
|
70
74
|
hover: true,
|
|
71
|
-
tooltipDelay: 300
|
|
75
|
+
tooltipDelay: 300,
|
|
72
76
|
// 300ms hover delay per spec
|
|
77
|
+
hideEdgesOnDrag: false,
|
|
78
|
+
hideEdgesOnZoom: false
|
|
73
79
|
},
|
|
74
80
|
nodes: {
|
|
75
81
|
shape: "dot",
|
|
76
|
-
size:
|
|
82
|
+
size: nodeSize,
|
|
77
83
|
font: {
|
|
78
|
-
size: 12,
|
|
84
|
+
size: showNodeLabels ? 12 : 0,
|
|
79
85
|
color: themeColors.text
|
|
80
86
|
},
|
|
81
87
|
borderWidth: 1,
|
|
@@ -90,7 +96,7 @@ function getDefaultNetworkOptions(themeColors) {
|
|
|
90
96
|
}
|
|
91
97
|
},
|
|
92
98
|
smooth: {
|
|
93
|
-
enabled:
|
|
99
|
+
enabled: curved,
|
|
94
100
|
type: "dynamic",
|
|
95
101
|
roundness: 0.5
|
|
96
102
|
},
|
|
@@ -110,6 +116,40 @@ function getDefaultNetworkOptions(themeColors) {
|
|
|
110
116
|
}
|
|
111
117
|
|
|
112
118
|
// src/parsers.ts
|
|
119
|
+
async function parseBackgroundQueryResponse(response) {
|
|
120
|
+
var _a, _b;
|
|
121
|
+
if (!response) return [];
|
|
122
|
+
try {
|
|
123
|
+
let data2;
|
|
124
|
+
if (typeof response.json === "function") {
|
|
125
|
+
data2 = await response.json();
|
|
126
|
+
} else if (typeof response.data === "string") {
|
|
127
|
+
data2 = JSON.parse(response.data);
|
|
128
|
+
} else if (typeof response === "object") {
|
|
129
|
+
data2 = response;
|
|
130
|
+
} else {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
const bindings = (_b = (_a = data2 == null ? void 0 : data2.results) == null ? void 0 : _a.bindings) != null ? _b : [];
|
|
134
|
+
const triples = [];
|
|
135
|
+
for (const binding of bindings) {
|
|
136
|
+
if (!binding.subject || !binding.predicate || !binding.object || typeof binding.subject.value !== "string" || typeof binding.predicate.value !== "string" || typeof binding.object.value !== "string") continue;
|
|
137
|
+
triples.push({
|
|
138
|
+
subject: binding.subject.value,
|
|
139
|
+
predicate: binding.predicate.value,
|
|
140
|
+
object: {
|
|
141
|
+
value: binding.object.value,
|
|
142
|
+
type: binding.object.type || "uri",
|
|
143
|
+
datatype: binding.object.datatype,
|
|
144
|
+
lang: binding.object["xml:lang"]
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return triples;
|
|
149
|
+
} catch (e) {
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
113
153
|
function parseConstructResults(yasrResults) {
|
|
114
154
|
const triples = [];
|
|
115
155
|
if (!yasrResults || !yasrResults.getBindings) {
|
|
@@ -152,10 +192,129 @@ function getNodeColor(node, triples, themeColors) {
|
|
|
152
192
|
return themeColors.uri;
|
|
153
193
|
}
|
|
154
194
|
|
|
195
|
+
// src/predicateIcons.ts
|
|
196
|
+
var PREDICATE_ICONS = {
|
|
197
|
+
// RDF core
|
|
198
|
+
"http://www.w3.org/1999/02/22-rdf-syntax-ns#type": "a",
|
|
199
|
+
// Turtle's "a" shorthand
|
|
200
|
+
"http://www.w3.org/1999/02/22-rdf-syntax-ns#value": "val",
|
|
201
|
+
// RDFS
|
|
202
|
+
"http://www.w3.org/2000/01/rdf-schema#label": "lbl",
|
|
203
|
+
"http://www.w3.org/2000/01/rdf-schema#comment": "cmt",
|
|
204
|
+
"http://www.w3.org/2000/01/rdf-schema#subClassOf": "\u2282",
|
|
205
|
+
// ⊂
|
|
206
|
+
"http://www.w3.org/2000/01/rdf-schema#subPropertyOf": "\u2286",
|
|
207
|
+
// ⊆
|
|
208
|
+
"http://www.w3.org/2000/01/rdf-schema#domain": "dom",
|
|
209
|
+
"http://www.w3.org/2000/01/rdf-schema#range": "rng",
|
|
210
|
+
"http://www.w3.org/2000/01/rdf-schema#seeAlso": "see",
|
|
211
|
+
"http://www.w3.org/2000/01/rdf-schema#isDefinedBy": "idb",
|
|
212
|
+
// OWL
|
|
213
|
+
"http://www.w3.org/2002/07/owl#sameAs": "\u2261",
|
|
214
|
+
// ≡
|
|
215
|
+
"http://www.w3.org/2002/07/owl#equivalentClass": "\u2245",
|
|
216
|
+
// ≅
|
|
217
|
+
"http://www.w3.org/2002/07/owl#inverseOf": "\u21C4",
|
|
218
|
+
// ⇄
|
|
219
|
+
"http://www.w3.org/2002/07/owl#disjointWith": "\u2260",
|
|
220
|
+
// ≠
|
|
221
|
+
// SKOS
|
|
222
|
+
"http://www.w3.org/2004/02/skos/core#prefLabel": "\u2605",
|
|
223
|
+
// ★
|
|
224
|
+
"http://www.w3.org/2004/02/skos/core#altLabel": "\u2606",
|
|
225
|
+
// ☆
|
|
226
|
+
"http://www.w3.org/2004/02/skos/core#definition": "def",
|
|
227
|
+
"http://www.w3.org/2004/02/skos/core#broader": "\u2191",
|
|
228
|
+
// ↑
|
|
229
|
+
"http://www.w3.org/2004/02/skos/core#narrower": "\u2193",
|
|
230
|
+
// ↓
|
|
231
|
+
"http://www.w3.org/2004/02/skos/core#related": "\u2194",
|
|
232
|
+
// ↔
|
|
233
|
+
"http://www.w3.org/2004/02/skos/core#note": "note",
|
|
234
|
+
"http://www.w3.org/2004/02/skos/core#exactMatch": "\u2261",
|
|
235
|
+
// ≡
|
|
236
|
+
"http://www.w3.org/2004/02/skos/core#closeMatch": "\u2248",
|
|
237
|
+
// ≈
|
|
238
|
+
// Dublin Core Terms
|
|
239
|
+
"http://purl.org/dc/terms/title": "ttl",
|
|
240
|
+
"http://purl.org/dc/terms/description": "dsc",
|
|
241
|
+
"http://purl.org/dc/terms/created": "crt",
|
|
242
|
+
"http://purl.org/dc/terms/modified": "mod",
|
|
243
|
+
"http://purl.org/dc/terms/creator": "by",
|
|
244
|
+
"http://purl.org/dc/terms/subject": "sbj",
|
|
245
|
+
// FOAF
|
|
246
|
+
"http://xmlns.com/foaf/0.1/name": "nm",
|
|
247
|
+
"http://xmlns.com/foaf/0.1/knows": "\u27F7",
|
|
248
|
+
// ⟷
|
|
249
|
+
"http://xmlns.com/foaf/0.1/member": "mbr",
|
|
250
|
+
// Schema.org
|
|
251
|
+
"http://schema.org/name": "nm",
|
|
252
|
+
"http://schema.org/description": "dsc"
|
|
253
|
+
};
|
|
254
|
+
function getPredicateIcon(predicateUri) {
|
|
255
|
+
return PREDICATE_ICONS[predicateUri];
|
|
256
|
+
}
|
|
257
|
+
|
|
155
258
|
// src/transformers.ts
|
|
156
|
-
|
|
259
|
+
var RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
|
|
260
|
+
var SCHEMA_IMAGE = "https://schema.org/image";
|
|
261
|
+
var SCHEMA_ICON = "https://schema.org/icon";
|
|
262
|
+
var RDFS_LABEL = "http://www.w3.org/2000/01/rdf-schema#label";
|
|
263
|
+
var RDFS_SUBCLASSOF = "http://www.w3.org/2000/01/rdf-schema#subClassOf";
|
|
264
|
+
var SUPPRESSED_PREDICATES = /* @__PURE__ */ new Set([SCHEMA_IMAGE, SCHEMA_ICON, RDFS_LABEL]);
|
|
265
|
+
function appendTooltipRows(title, rows) {
|
|
266
|
+
const closingTag = "</div>";
|
|
267
|
+
const idx = title.lastIndexOf(closingTag);
|
|
268
|
+
if (idx === -1) return title + rows;
|
|
269
|
+
return title.slice(0, idx) + rows + closingTag;
|
|
270
|
+
}
|
|
271
|
+
function buildVisualTooltipRow(key, value) {
|
|
272
|
+
return `<div class="yasgui-tooltip-row"><span class="yasgui-tooltip-key">${escapeHtml(key)}</span><span class="yasgui-tooltip-val">${escapeHtml(value)}</span></div>`;
|
|
273
|
+
}
|
|
274
|
+
function escapeHtml(str) {
|
|
275
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
276
|
+
}
|
|
277
|
+
function createCompactNodeTooltipHTML(uri, triples, prefixMap) {
|
|
278
|
+
const isBlankNode = uri.startsWith("_:");
|
|
279
|
+
let rows = `<div class="yasgui-tooltip-type">${isBlankNode ? "Blank Node" : "URI"}</div>`;
|
|
280
|
+
rows += `<div class="yasgui-tooltip-row"><span class="yasgui-tooltip-key">${isBlankNode ? "Identifier" : "Full URI"}</span><span class="yasgui-tooltip-val">${escapeHtml(uri)}</span></div>`;
|
|
281
|
+
triples.filter((t) => t.subject === uri && t.predicate === RDF_TYPE).forEach((t) => {
|
|
282
|
+
const typeLabel = applyPrefix(t.object.value, prefixMap);
|
|
283
|
+
rows += `<div class="yasgui-tooltip-row"><span class="yasgui-tooltip-key">rdf:type</span><span class="yasgui-tooltip-val">${escapeHtml(typeLabel)}</span></div>`;
|
|
284
|
+
});
|
|
285
|
+
triples.filter((t) => t.subject === uri && t.object.type === "literal").forEach((t) => {
|
|
286
|
+
const predLabel = applyPrefix(t.predicate, prefixMap);
|
|
287
|
+
rows += `<div class="yasgui-tooltip-row"><span class="yasgui-tooltip-key">${escapeHtml(predLabel)}</span><span class="yasgui-tooltip-val">${escapeHtml(t.object.value)}</span></div>`;
|
|
288
|
+
});
|
|
289
|
+
return `<div class="yasgui-graph-tooltip">${rows}</div>`;
|
|
290
|
+
}
|
|
291
|
+
function createNodeTooltipHTML(nodeType, value, datatype, lang, prefixMap) {
|
|
292
|
+
const typeLabel = nodeType === "uri" ? "URI" : nodeType === "literal" ? "Literal" : "Blank Node";
|
|
293
|
+
let rows = `<div class="yasgui-tooltip-type">${typeLabel}</div>`;
|
|
294
|
+
if (nodeType === "literal") {
|
|
295
|
+
rows += `<div class="yasgui-tooltip-row"><span class="yasgui-tooltip-key">Value</span><span class="yasgui-tooltip-val">${escapeHtml(value)}</span></div>`;
|
|
296
|
+
if (datatype) {
|
|
297
|
+
const dtLabel = prefixMap ? applyPrefix(datatype, prefixMap) : datatype;
|
|
298
|
+
rows += `<div class="yasgui-tooltip-row"><span class="yasgui-tooltip-key">Datatype</span><span class="yasgui-tooltip-val">${escapeHtml(dtLabel)}</span></div>`;
|
|
299
|
+
}
|
|
300
|
+
if (lang) {
|
|
301
|
+
rows += `<div class="yasgui-tooltip-row"><span class="yasgui-tooltip-key">Language</span><span class="yasgui-tooltip-val">${escapeHtml(lang)}</span></div>`;
|
|
302
|
+
}
|
|
303
|
+
} else if (nodeType === "uri") {
|
|
304
|
+
rows += `<div class="yasgui-tooltip-row"><span class="yasgui-tooltip-key">Full URI</span><span class="yasgui-tooltip-val">${escapeHtml(value)}</span></div>`;
|
|
305
|
+
} else if (nodeType === "bnode") {
|
|
306
|
+
rows += `<div class="yasgui-tooltip-row"><span class="yasgui-tooltip-key">Identifier</span><span class="yasgui-tooltip-val">${escapeHtml(value)}</span></div>`;
|
|
307
|
+
}
|
|
308
|
+
return `<div class="yasgui-graph-tooltip">${rows}</div>`;
|
|
309
|
+
}
|
|
310
|
+
function createEdgeTooltipHTML(predicateUri) {
|
|
311
|
+
const rows = `<div class="yasgui-tooltip-type">Predicate</div><div class="yasgui-tooltip-row"><span class="yasgui-tooltip-key">Full URI</span><span class="yasgui-tooltip-val">${escapeHtml(predicateUri)}</span></div>`;
|
|
312
|
+
return `<div class="yasgui-graph-tooltip">${rows}</div>`;
|
|
313
|
+
}
|
|
314
|
+
function createNodeMap(triples, prefixMap, themeColors, settings) {
|
|
157
315
|
const nodeMap = /* @__PURE__ */ new Map();
|
|
158
316
|
let nodeId = 1;
|
|
317
|
+
const sizeMultiplier = (settings == null ? void 0 : settings.nodeSize) === "small" ? 0.5 : (settings == null ? void 0 : settings.nodeSize) === "large" ? 2 : 1;
|
|
159
318
|
triples.forEach((triple) => {
|
|
160
319
|
if (!nodeMap.has(triple.subject)) {
|
|
161
320
|
const isBlankNode = triple.subject.startsWith("_:");
|
|
@@ -167,11 +326,18 @@ function createNodeMap(triples, prefixMap, themeColors) {
|
|
|
167
326
|
color: getNodeColor({ uri: triple.subject, type: "uri" }, triples, themeColors),
|
|
168
327
|
type: "uri",
|
|
169
328
|
fullValue: triple.subject,
|
|
170
|
-
|
|
329
|
+
size: 10 * sizeMultiplier,
|
|
330
|
+
title: createNodeTooltipHTML(
|
|
331
|
+
isBlankNode ? "bnode" : "uri",
|
|
332
|
+
triple.subject,
|
|
333
|
+
void 0,
|
|
334
|
+
void 0,
|
|
335
|
+
prefixMap
|
|
336
|
+
)
|
|
171
337
|
});
|
|
172
338
|
}
|
|
173
339
|
const objValue = triple.object.value;
|
|
174
|
-
if (!nodeMap.has(objValue)) {
|
|
340
|
+
if (!nodeMap.has(objValue) && !SUPPRESSED_PREDICATES.has(triple.predicate)) {
|
|
175
341
|
const isLiteral = triple.object.type === "literal";
|
|
176
342
|
const isBlankNode = !isLiteral && objValue.startsWith("_:");
|
|
177
343
|
let label;
|
|
@@ -180,15 +346,21 @@ function createNodeMap(triples, prefixMap, themeColors) {
|
|
|
180
346
|
if (isLiteral) {
|
|
181
347
|
label = truncateLabel(objValue);
|
|
182
348
|
fullValue = objValue;
|
|
183
|
-
title =
|
|
349
|
+
title = createNodeTooltipHTML(
|
|
350
|
+
"literal",
|
|
351
|
+
objValue,
|
|
352
|
+
triple.object.datatype,
|
|
353
|
+
triple.object.lang,
|
|
354
|
+
prefixMap
|
|
355
|
+
);
|
|
184
356
|
} else if (isBlankNode) {
|
|
185
357
|
label = objValue;
|
|
186
358
|
fullValue = objValue;
|
|
187
|
-
title = objValue;
|
|
359
|
+
title = createNodeTooltipHTML("bnode", objValue);
|
|
188
360
|
} else {
|
|
189
361
|
label = truncateLabel(applyPrefix(objValue, prefixMap));
|
|
190
362
|
fullValue = objValue;
|
|
191
|
-
title =
|
|
363
|
+
title = createNodeTooltipHTML("uri", objValue, void 0, void 0, prefixMap);
|
|
192
364
|
}
|
|
193
365
|
nodeMap.set(objValue, {
|
|
194
366
|
id: nodeId++,
|
|
@@ -201,39 +373,157 @@ function createNodeMap(triples, prefixMap, themeColors) {
|
|
|
201
373
|
),
|
|
202
374
|
type: isLiteral ? "literal" : "uri",
|
|
203
375
|
fullValue,
|
|
376
|
+
size: (isLiteral ? 5 : 10) * sizeMultiplier,
|
|
204
377
|
title
|
|
205
378
|
});
|
|
206
379
|
}
|
|
207
380
|
});
|
|
208
381
|
return nodeMap;
|
|
209
382
|
}
|
|
210
|
-
function createEdgesArray(triples, nodeMap, prefixMap) {
|
|
383
|
+
function createEdgesArray(triples, nodeMap, prefixMap, settings) {
|
|
211
384
|
const edges = [];
|
|
212
385
|
const edgeSet = /* @__PURE__ */ new Set();
|
|
213
386
|
triples.forEach((triple) => {
|
|
387
|
+
var _a, _b;
|
|
388
|
+
if (SUPPRESSED_PREDICATES.has(triple.predicate)) return;
|
|
214
389
|
const fromNode = nodeMap.get(triple.subject);
|
|
215
390
|
const toNode = nodeMap.get(triple.object.value);
|
|
216
391
|
if (!fromNode || !toNode) return;
|
|
217
392
|
const edgeKey = `${fromNode.id}-${triple.predicate}-${toNode.id}`;
|
|
218
393
|
if (!edgeSet.has(edgeKey)) {
|
|
219
394
|
edgeSet.add(edgeKey);
|
|
395
|
+
let edgeLabel;
|
|
396
|
+
const predicateDisplay = (_a = settings == null ? void 0 : settings.predicateDisplay) != null ? _a : "label";
|
|
397
|
+
if (predicateDisplay === "none") {
|
|
398
|
+
edgeLabel = "";
|
|
399
|
+
} else if (predicateDisplay === "icon") {
|
|
400
|
+
edgeLabel = (_b = getPredicateIcon(triple.predicate)) != null ? _b : truncateLabel(applyPrefix(triple.predicate, prefixMap));
|
|
401
|
+
} else {
|
|
402
|
+
edgeLabel = truncateLabel(applyPrefix(triple.predicate, prefixMap));
|
|
403
|
+
}
|
|
220
404
|
edges.push({
|
|
221
405
|
id: `edge_${fromNode.id}_${toNode.id}_${edges.length}`,
|
|
222
406
|
from: fromNode.id,
|
|
223
407
|
to: toNode.id,
|
|
224
|
-
label:
|
|
408
|
+
label: edgeLabel,
|
|
225
409
|
predicate: triple.predicate,
|
|
226
|
-
title:
|
|
410
|
+
title: createEdgeTooltipHTML(triple.predicate),
|
|
227
411
|
arrows: "to"
|
|
228
412
|
});
|
|
229
413
|
}
|
|
230
414
|
});
|
|
231
415
|
return edges;
|
|
232
416
|
}
|
|
233
|
-
function
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
417
|
+
function isNodeVisible(node, triples, settings) {
|
|
418
|
+
if (node.uri && node.uri.startsWith("_:")) {
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
421
|
+
if (!settings.compactMode) {
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
if (node.type === "literal") {
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
const isClass = triples.some(
|
|
428
|
+
(t) => t.predicate === "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" && t.object.value === node.uri
|
|
429
|
+
);
|
|
430
|
+
if (isClass) {
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
function triplesToGraph(triples, prefixMap, themeColors, settings) {
|
|
436
|
+
const nodeMap = createNodeMap(triples, prefixMap, themeColors, settings);
|
|
437
|
+
const sizeMultiplier = (settings == null ? void 0 : settings.nodeSize) === "small" ? 0.5 : (settings == null ? void 0 : settings.nodeSize) === "large" ? 2 : 1;
|
|
438
|
+
const tripleIndex = /* @__PURE__ */ new Map();
|
|
439
|
+
for (const t of triples) {
|
|
440
|
+
if (!tripleIndex.has(t.subject)) tripleIndex.set(t.subject, /* @__PURE__ */ new Map());
|
|
441
|
+
const sp = tripleIndex.get(t.subject);
|
|
442
|
+
if (!sp.has(t.predicate)) sp.set(t.predicate, []);
|
|
443
|
+
sp.get(t.predicate).push(t.object.value);
|
|
444
|
+
}
|
|
445
|
+
function getNodeVisualIdx(uri) {
|
|
446
|
+
const sp = tripleIndex.get(uri);
|
|
447
|
+
if (!sp) return {};
|
|
448
|
+
const icons = sp.get(SCHEMA_ICON);
|
|
449
|
+
if (icons == null ? void 0 : icons.length) return { icon: icons[0] };
|
|
450
|
+
const images = sp.get(SCHEMA_IMAGE);
|
|
451
|
+
if (images == null ? void 0 : images.length) return { image: images[0] };
|
|
452
|
+
return {};
|
|
453
|
+
}
|
|
454
|
+
function resolveCompactVisualIdx(uri) {
|
|
455
|
+
var _a, _b, _c, _d;
|
|
456
|
+
const own = getNodeVisualIdx(uri);
|
|
457
|
+
if (own.icon || own.image) return own;
|
|
458
|
+
const typeUris = (_b = (_a = tripleIndex.get(uri)) == null ? void 0 : _a.get(RDF_TYPE)) != null ? _b : [];
|
|
459
|
+
for (const typeUri of typeUris) {
|
|
460
|
+
const cv = getNodeVisualIdx(typeUri);
|
|
461
|
+
if (cv.icon || cv.image) return cv;
|
|
462
|
+
}
|
|
463
|
+
for (const typeUri of typeUris) {
|
|
464
|
+
const superUris = (_d = (_c = tripleIndex.get(typeUri)) == null ? void 0 : _c.get(RDFS_SUBCLASSOF)) != null ? _d : [];
|
|
465
|
+
for (const superUri of superUris) {
|
|
466
|
+
const sv = getNodeVisualIdx(superUri);
|
|
467
|
+
if (sv.icon || sv.image) return sv;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return {};
|
|
471
|
+
}
|
|
472
|
+
if (settings == null ? void 0 : settings.compactMode) {
|
|
473
|
+
const subjects = new Set(triples.map((t) => t.subject));
|
|
474
|
+
subjects.forEach((subjectUri) => {
|
|
475
|
+
const node = nodeMap.get(subjectUri);
|
|
476
|
+
if (node) {
|
|
477
|
+
node.title = createCompactNodeTooltipHTML(subjectUri, triples, prefixMap);
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
nodeMap.forEach((node) => {
|
|
482
|
+
var _a, _b;
|
|
483
|
+
if (!node.uri || node.type === "literal") return;
|
|
484
|
+
const visual = (settings == null ? void 0 : settings.compactMode) ? resolveCompactVisualIdx(node.uri) : getNodeVisualIdx(node.uri);
|
|
485
|
+
const rdfsLabel = (_b = (_a = tripleIndex.get(node.uri)) == null ? void 0 : _a.get(RDFS_LABEL)) == null ? void 0 : _b[0];
|
|
486
|
+
if (visual.icon) {
|
|
487
|
+
node.shape = "text";
|
|
488
|
+
node.label = rdfsLabel ? `${visual.icon}
|
|
489
|
+
${rdfsLabel}` : visual.icon;
|
|
490
|
+
node.font = { size: 14 * sizeMultiplier };
|
|
491
|
+
if (!(settings == null ? void 0 : settings.compactMode)) {
|
|
492
|
+
node.title = appendTooltipRows(node.title, buildVisualTooltipRow("Icon", visual.icon));
|
|
493
|
+
}
|
|
494
|
+
} else if (visual.image) {
|
|
495
|
+
let imageAllowed = false;
|
|
496
|
+
try {
|
|
497
|
+
const parsed = new URL(visual.image);
|
|
498
|
+
imageAllowed = parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
499
|
+
} catch (e) {
|
|
500
|
+
}
|
|
501
|
+
if (imageAllowed) {
|
|
502
|
+
node.shape = "circularImage";
|
|
503
|
+
node.image = visual.image;
|
|
504
|
+
}
|
|
505
|
+
if (rdfsLabel) {
|
|
506
|
+
node.label = rdfsLabel;
|
|
507
|
+
node.font = { size: 14 * sizeMultiplier };
|
|
508
|
+
}
|
|
509
|
+
if (!(settings == null ? void 0 : settings.compactMode)) {
|
|
510
|
+
node.title = appendTooltipRows(node.title, buildVisualTooltipRow("Image", visual.image));
|
|
511
|
+
}
|
|
512
|
+
} else if (rdfsLabel) {
|
|
513
|
+
node.label = rdfsLabel;
|
|
514
|
+
}
|
|
515
|
+
if (rdfsLabel && !(settings == null ? void 0 : settings.compactMode)) {
|
|
516
|
+
node.title = appendTooltipRows(node.title, buildVisualTooltipRow("rdfs:label", rdfsLabel));
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
const visibleNodeIds = /* @__PURE__ */ new Set();
|
|
520
|
+
nodeMap.forEach((node) => {
|
|
521
|
+
if (!settings || isNodeVisible(node, triples, settings)) {
|
|
522
|
+
visibleNodeIds.add(node.id);
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
const edges = createEdgesArray(triples, nodeMap, prefixMap, settings).filter((e) => visibleNodeIds.has(e.from) && visibleNodeIds.has(e.to));
|
|
526
|
+
const nodes = Array.from(nodeMap.values()).filter((n) => visibleNodeIds.has(n.id));
|
|
237
527
|
return { nodes, edges };
|
|
238
528
|
}
|
|
239
529
|
|
|
@@ -29704,9 +29994,44 @@ function watchThemeChanges(callback) {
|
|
|
29704
29994
|
return observer;
|
|
29705
29995
|
}
|
|
29706
29996
|
|
|
29997
|
+
// src/settings.ts
|
|
29998
|
+
var DEFAULT_SETTINGS = {
|
|
29999
|
+
edgeStyle: "curved",
|
|
30000
|
+
compactMode: false,
|
|
30001
|
+
predicateDisplay: "icon",
|
|
30002
|
+
showNodeLabels: true,
|
|
30003
|
+
physicsEnabled: true,
|
|
30004
|
+
nodeSize: "medium"
|
|
30005
|
+
};
|
|
30006
|
+
var STORAGE_KEY = "yasgui-graph-plugin-settings";
|
|
30007
|
+
function loadSettings() {
|
|
30008
|
+
try {
|
|
30009
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
30010
|
+
if (stored) {
|
|
30011
|
+
return { ...DEFAULT_SETTINGS, ...JSON.parse(stored) };
|
|
30012
|
+
}
|
|
30013
|
+
} catch (e) {
|
|
30014
|
+
}
|
|
30015
|
+
return { ...DEFAULT_SETTINGS };
|
|
30016
|
+
}
|
|
30017
|
+
function saveSettings(settings) {
|
|
30018
|
+
try {
|
|
30019
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
|
|
30020
|
+
} catch (e) {
|
|
30021
|
+
}
|
|
30022
|
+
}
|
|
30023
|
+
|
|
29707
30024
|
// src/GraphPlugin.ts
|
|
30025
|
+
var LOADING_BORDER_WIDTH = 4;
|
|
30026
|
+
var LOADING_BORDER_COLOR = "#ffa500";
|
|
30027
|
+
var EXPANDED_BORDER_WIDTH = 3;
|
|
30028
|
+
var DEFAULT_BORDER_WIDTH = 2;
|
|
29708
30029
|
var GraphPlugin = class {
|
|
29709
30030
|
constructor(yasr) {
|
|
30031
|
+
this.settingsPanelOpen = false;
|
|
30032
|
+
this.clickOutsideHandler = null;
|
|
30033
|
+
this.expansionAbortController = null;
|
|
30034
|
+
this.uriToNodeId = /* @__PURE__ */ new Map();
|
|
29710
30035
|
this.yasr = yasr;
|
|
29711
30036
|
this.network = null;
|
|
29712
30037
|
this.currentTheme = getCurrentTheme();
|
|
@@ -29716,6 +30041,7 @@ var GraphPlugin = class {
|
|
|
29716
30041
|
this.edgesDataSet = null;
|
|
29717
30042
|
this.triples = null;
|
|
29718
30043
|
this.prefixMap = null;
|
|
30044
|
+
this.settings = loadSettings();
|
|
29719
30045
|
}
|
|
29720
30046
|
/**
|
|
29721
30047
|
* Plugin priority (higher = shown first in tabs)
|
|
@@ -29729,6 +30055,12 @@ var GraphPlugin = class {
|
|
|
29729
30055
|
static get label() {
|
|
29730
30056
|
return "Graph";
|
|
29731
30057
|
}
|
|
30058
|
+
/**
|
|
30059
|
+
* Help/documentation URL
|
|
30060
|
+
*/
|
|
30061
|
+
static get helpReference() {
|
|
30062
|
+
return "https://yasgui-doc.matdata.eu/docs/user-guide#graph-plugin";
|
|
30063
|
+
}
|
|
29732
30064
|
/**
|
|
29733
30065
|
* Check if plugin can handle the current results
|
|
29734
30066
|
* @returns True if results are from CONSTRUCT or DESCRIBE query
|
|
@@ -29749,6 +30081,13 @@ var GraphPlugin = class {
|
|
|
29749
30081
|
* Render the graph visualization
|
|
29750
30082
|
*/
|
|
29751
30083
|
draw() {
|
|
30084
|
+
const wasPanelOpen = this.settingsPanelOpen;
|
|
30085
|
+
if (this.expansionAbortController) {
|
|
30086
|
+
this.expansionAbortController.abort();
|
|
30087
|
+
this.expansionAbortController = null;
|
|
30088
|
+
}
|
|
30089
|
+
this.expandedNodes = /* @__PURE__ */ new Set();
|
|
30090
|
+
this.uriToNodeId = /* @__PURE__ */ new Map();
|
|
29752
30091
|
this.yasr.resultsEl.innerHTML = "";
|
|
29753
30092
|
try {
|
|
29754
30093
|
this.triples = parseConstructResults(this.yasr.results);
|
|
@@ -29765,24 +30104,52 @@ var GraphPlugin = class {
|
|
|
29765
30104
|
this.prefixMap = extractPrefixes(this.yasr);
|
|
29766
30105
|
this.currentTheme = getCurrentTheme();
|
|
29767
30106
|
const themeColors = getThemeNodeColors(this.currentTheme);
|
|
29768
|
-
const { nodes, edges } = triplesToGraph(this.triples, this.prefixMap, themeColors);
|
|
30107
|
+
const { nodes, edges } = triplesToGraph(this.triples, this.prefixMap, themeColors, this.settings);
|
|
29769
30108
|
const container = document.createElement("div");
|
|
29770
30109
|
container.className = "yasgui-graph-plugin-container";
|
|
29771
30110
|
container.id = "yasgui-graph-plugin-container";
|
|
29772
30111
|
this.yasr.resultsEl.appendChild(container);
|
|
29773
30112
|
this.nodesDataSet = new DataSet(nodes);
|
|
29774
30113
|
this.edgesDataSet = new DataSet(edges);
|
|
29775
|
-
const options = getDefaultNetworkOptions(themeColors);
|
|
30114
|
+
const options = getDefaultNetworkOptions(themeColors, this.settings);
|
|
29776
30115
|
this.network = new Network(
|
|
29777
30116
|
container,
|
|
29778
30117
|
{ nodes: this.nodesDataSet, edges: this.edgesDataSet },
|
|
29779
30118
|
options
|
|
29780
30119
|
);
|
|
30120
|
+
this.setupHtmlTooltips(container);
|
|
29781
30121
|
this.applyCanvasBackground(themeColors.background);
|
|
29782
|
-
this.
|
|
29783
|
-
|
|
29784
|
-
this.network.
|
|
29785
|
-
|
|
30122
|
+
this.setupContainerResize(container);
|
|
30123
|
+
if (this.settings.physicsEnabled) {
|
|
30124
|
+
this.network.on("stabilizationIterationsDone", () => {
|
|
30125
|
+
this.network.setOptions({ physics: { enabled: true } });
|
|
30126
|
+
this.network.fit({ maxZoomLevel: 3 });
|
|
30127
|
+
});
|
|
30128
|
+
} else {
|
|
30129
|
+
setTimeout(() => {
|
|
30130
|
+
if (this.network) {
|
|
30131
|
+
this.network.fit({ maxZoomLevel: 3 });
|
|
30132
|
+
}
|
|
30133
|
+
}, 100);
|
|
30134
|
+
}
|
|
30135
|
+
this.network.on("dragEnd", (params) => {
|
|
30136
|
+
if (params.nodes.length > 0) {
|
|
30137
|
+
const positions = this.network.getPositions(params.nodes);
|
|
30138
|
+
const updates = params.nodes.map((id2) => ({
|
|
30139
|
+
id: id2,
|
|
30140
|
+
x: positions[id2].x,
|
|
30141
|
+
y: positions[id2].y,
|
|
30142
|
+
fixed: { x: true, y: true }
|
|
30143
|
+
}));
|
|
30144
|
+
this.nodesDataSet.update(updates);
|
|
30145
|
+
}
|
|
30146
|
+
});
|
|
30147
|
+
this.setupNodeExpansion();
|
|
30148
|
+
this.uriToNodeId = /* @__PURE__ */ new Map();
|
|
30149
|
+
this.nodesDataSet.get().forEach((node) => {
|
|
30150
|
+
if (node.uri) {
|
|
30151
|
+
this.uriToNodeId.set(node.uri, node.id);
|
|
30152
|
+
}
|
|
29786
30153
|
});
|
|
29787
30154
|
if (!this.themeObserver) {
|
|
29788
30155
|
this.themeObserver = watchThemeChanges((newTheme) => {
|
|
@@ -29801,6 +30168,20 @@ var GraphPlugin = class {
|
|
|
29801
30168
|
}
|
|
29802
30169
|
};
|
|
29803
30170
|
controls.appendChild(fitButton);
|
|
30171
|
+
const settingsButton = document.createElement("button");
|
|
30172
|
+
settingsButton.className = "yasgui-graph-button yasgui-graph-settings-button";
|
|
30173
|
+
settingsButton.setAttribute("aria-label", "Graph settings");
|
|
30174
|
+
settingsButton.innerHTML = `<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
30175
|
+
<path d="M8 10.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z"/>
|
|
30176
|
+
<path d="M9.796 1.343c-.527-1.79-3.065-1.79-3.592 0l-.094.319a.873.873 0 0 1-1.255.52l-.292-.16c-1.64-.892-3.434.901-2.54 2.541l.159.292a.873.873 0 0 1-.52 1.255l-.319.094c-1.79.527-1.79 3.065 0 3.592l.319.094a.873.873 0 0 1 .52 1.255l-.16.292c-.892 1.64.901 3.434 2.541 2.54l.292-.159a.873.873 0 0 1 1.255.52l.094.319c.527 1.79 3.065 1.79 3.592 0l.094-.319a.873.873 0 0 1 1.255-.52l.292.16c1.64.892 3.434-.901 2.54-2.541l-.159-.292a.873.873 0 0 1 .52-1.255l.319-.094c1.79-.527 1.79-3.065 0-3.592l-.319-.094a.873.873 0 0 1-.52-1.255l.16-.292c.892-1.64-.901-3.434-2.541-2.54l-.292.159a.873.873 0 0 1-1.255-.52l-.094-.319zm-2.633.283c.246-.835 1.428-.835 1.674 0l.094.319a1.873 1.873 0 0 0 2.693 1.115l.291-.16c.764-.415 1.6.42 1.184 1.185l-.159.292a1.873 1.873 0 0 0 1.116 2.692l.318.094c.835.246.835 1.428 0 1.674l-.319.094a1.873 1.873 0 0 0-1.115 2.693l.16.291c.415.764-.42 1.6-1.185 1.184l-.291-.159a1.873 1.873 0 0 0-2.693 1.116l-.094.318c-.246.835-1.428.835-1.674 0l-.094-.319a1.873 1.873 0 0 0-2.692-1.115l-.292.16c-.764.415-1.6-.42-1.184-1.185l.159-.291A1.873 1.873 0 0 0 1.945 8.93l-.319-.094c-.835-.246-.835-1.428 0-1.674l.319-.094A1.873 1.873 0 0 0 3.06 4.474l-.16-.292c-.415-.764.42-1.6 1.185-1.184l.292.159a1.873 1.873 0 0 0 2.692-1.115l.094-.319z"/>
|
|
30177
|
+
</svg> Settings`;
|
|
30178
|
+
settingsButton.onclick = () => {
|
|
30179
|
+
this.toggleSettingsPanel(container);
|
|
30180
|
+
};
|
|
30181
|
+
controls.appendChild(settingsButton);
|
|
30182
|
+
if (wasPanelOpen) {
|
|
30183
|
+
this.toggleSettingsPanel(container);
|
|
30184
|
+
}
|
|
29804
30185
|
} catch (error) {
|
|
29805
30186
|
console.error("Error rendering graph:", error);
|
|
29806
30187
|
const errorDiv = document.createElement("div");
|
|
@@ -29809,6 +30190,111 @@ var GraphPlugin = class {
|
|
|
29809
30190
|
this.yasr.resultsEl.appendChild(errorDiv);
|
|
29810
30191
|
}
|
|
29811
30192
|
}
|
|
30193
|
+
/**
|
|
30194
|
+
* Setup custom HTML tooltip rendering for vis-network
|
|
30195
|
+
* @param container - The graph container element
|
|
30196
|
+
*/
|
|
30197
|
+
setupHtmlTooltips(container) {
|
|
30198
|
+
if (!this.network) return;
|
|
30199
|
+
let hideTimeout = null;
|
|
30200
|
+
this.network.on("hoverNode", (params) => {
|
|
30201
|
+
if (hideTimeout) {
|
|
30202
|
+
clearTimeout(hideTimeout);
|
|
30203
|
+
hideTimeout = null;
|
|
30204
|
+
}
|
|
30205
|
+
const nodeId = params.node;
|
|
30206
|
+
const node = this.nodesDataSet.get(nodeId);
|
|
30207
|
+
if (node && node.title) {
|
|
30208
|
+
this.showHtmlTooltip(container, node.title, params.pointer.DOM);
|
|
30209
|
+
}
|
|
30210
|
+
});
|
|
30211
|
+
this.network.on("hoverEdge", (params) => {
|
|
30212
|
+
if (hideTimeout) {
|
|
30213
|
+
clearTimeout(hideTimeout);
|
|
30214
|
+
hideTimeout = null;
|
|
30215
|
+
}
|
|
30216
|
+
const edgeId = params.edge;
|
|
30217
|
+
const edge = this.edgesDataSet.get(edgeId);
|
|
30218
|
+
if (edge && edge.title) {
|
|
30219
|
+
this.showHtmlTooltip(container, edge.title, params.pointer.DOM);
|
|
30220
|
+
}
|
|
30221
|
+
});
|
|
30222
|
+
this.network.on("blurNode", () => {
|
|
30223
|
+
hideTimeout = window.setTimeout(() => {
|
|
30224
|
+
this.hideHtmlTooltipIfNotHovered(container);
|
|
30225
|
+
}, 200);
|
|
30226
|
+
});
|
|
30227
|
+
this.network.on("blurEdge", () => {
|
|
30228
|
+
hideTimeout = window.setTimeout(() => {
|
|
30229
|
+
this.hideHtmlTooltipIfNotHovered(container);
|
|
30230
|
+
}, 200);
|
|
30231
|
+
});
|
|
30232
|
+
this.network.on("dragStart", () => {
|
|
30233
|
+
if (hideTimeout) {
|
|
30234
|
+
clearTimeout(hideTimeout);
|
|
30235
|
+
hideTimeout = null;
|
|
30236
|
+
}
|
|
30237
|
+
this.hideHtmlTooltip(container);
|
|
30238
|
+
});
|
|
30239
|
+
this.network.on("zoom", () => {
|
|
30240
|
+
if (hideTimeout) {
|
|
30241
|
+
clearTimeout(hideTimeout);
|
|
30242
|
+
hideTimeout = null;
|
|
30243
|
+
}
|
|
30244
|
+
this.hideHtmlTooltip(container);
|
|
30245
|
+
});
|
|
30246
|
+
}
|
|
30247
|
+
/**
|
|
30248
|
+
* Show HTML tooltip at specified position
|
|
30249
|
+
* @param container - Container element
|
|
30250
|
+
* @param htmlContent - HTML content to display
|
|
30251
|
+
* @param position - Mouse position {x, y}
|
|
30252
|
+
*/
|
|
30253
|
+
showHtmlTooltip(container, htmlContent, position) {
|
|
30254
|
+
this.hideHtmlTooltip(container);
|
|
30255
|
+
const tooltip = document.createElement("div");
|
|
30256
|
+
tooltip.className = "yasgui-graph-tooltip-container";
|
|
30257
|
+
tooltip.innerHTML = htmlContent;
|
|
30258
|
+
tooltip.style.position = "absolute";
|
|
30259
|
+
tooltip.style.left = `${position.x + 10}px`;
|
|
30260
|
+
tooltip.style.top = `${position.y + 10}px`;
|
|
30261
|
+
tooltip.style.zIndex = "1000";
|
|
30262
|
+
tooltip.addEventListener("mouseleave", () => {
|
|
30263
|
+
this.hideHtmlTooltip(container);
|
|
30264
|
+
});
|
|
30265
|
+
container.appendChild(tooltip);
|
|
30266
|
+
const rect = tooltip.getBoundingClientRect();
|
|
30267
|
+
const containerRect = container.getBoundingClientRect();
|
|
30268
|
+
if (rect.right > containerRect.right) {
|
|
30269
|
+
tooltip.style.left = `${position.x - rect.width - 10}px`;
|
|
30270
|
+
}
|
|
30271
|
+
if (rect.bottom > containerRect.bottom) {
|
|
30272
|
+
tooltip.style.top = `${position.y - rect.height - 10}px`;
|
|
30273
|
+
}
|
|
30274
|
+
}
|
|
30275
|
+
/**
|
|
30276
|
+
* Hide HTML tooltip
|
|
30277
|
+
* @param container - Container element
|
|
30278
|
+
*/
|
|
30279
|
+
hideHtmlTooltip(container) {
|
|
30280
|
+
const existingTooltip = container.querySelector(".yasgui-graph-tooltip-container");
|
|
30281
|
+
if (existingTooltip) {
|
|
30282
|
+
existingTooltip.remove();
|
|
30283
|
+
}
|
|
30284
|
+
}
|
|
30285
|
+
/**
|
|
30286
|
+
* Hide HTML tooltip only if mouse is not hovering over it
|
|
30287
|
+
* @param container - Container element
|
|
30288
|
+
*/
|
|
30289
|
+
hideHtmlTooltipIfNotHovered(container) {
|
|
30290
|
+
const existingTooltip = container.querySelector(".yasgui-graph-tooltip-container");
|
|
30291
|
+
if (existingTooltip) {
|
|
30292
|
+
const isHovered = existingTooltip.matches(":hover");
|
|
30293
|
+
if (!isHovered) {
|
|
30294
|
+
existingTooltip.remove();
|
|
30295
|
+
}
|
|
30296
|
+
}
|
|
30297
|
+
}
|
|
29812
30298
|
/**
|
|
29813
30299
|
* Apply theme to existing network
|
|
29814
30300
|
* @param newTheme - 'light' or 'dark'
|
|
@@ -29819,12 +30305,30 @@ var GraphPlugin = class {
|
|
|
29819
30305
|
}
|
|
29820
30306
|
this.currentTheme = newTheme;
|
|
29821
30307
|
const themeColors = getThemeNodeColors(newTheme);
|
|
29822
|
-
const
|
|
30308
|
+
const fixedNodes = {};
|
|
30309
|
+
this.nodesDataSet.get().forEach((node) => {
|
|
30310
|
+
var _a, _b;
|
|
30311
|
+
const isFixed = node.fixed === true || typeof node.fixed === "object" && ((_a = node.fixed) == null ? void 0 : _a.x) && ((_b = node.fixed) == null ? void 0 : _b.y);
|
|
30312
|
+
if (isFixed && node.x !== void 0 && node.y !== void 0) {
|
|
30313
|
+
fixedNodes[node.id] = { x: node.x, y: node.y };
|
|
30314
|
+
}
|
|
30315
|
+
});
|
|
30316
|
+
const { nodes, edges } = triplesToGraph(this.triples, this.prefixMap, themeColors, this.settings);
|
|
29823
30317
|
this.nodesDataSet.clear();
|
|
29824
30318
|
this.nodesDataSet.add(nodes);
|
|
29825
30319
|
this.edgesDataSet.clear();
|
|
29826
30320
|
this.edgesDataSet.add(edges);
|
|
29827
|
-
const
|
|
30321
|
+
const fixedIds = Object.keys(fixedNodes);
|
|
30322
|
+
if (fixedIds.length > 0) {
|
|
30323
|
+
const updates = fixedIds.map((id2) => ({
|
|
30324
|
+
id: id2,
|
|
30325
|
+
x: fixedNodes[id2].x,
|
|
30326
|
+
y: fixedNodes[id2].y,
|
|
30327
|
+
fixed: { x: true, y: true }
|
|
30328
|
+
}));
|
|
30329
|
+
this.nodesDataSet.update(updates);
|
|
30330
|
+
}
|
|
30331
|
+
const options = getDefaultNetworkOptions(themeColors, this.settings);
|
|
29828
30332
|
this.network.setOptions(options);
|
|
29829
30333
|
this.applyCanvasBackground(themeColors.background);
|
|
29830
30334
|
}
|
|
@@ -29861,7 +30365,287 @@ var GraphPlugin = class {
|
|
|
29861
30365
|
this.resizeObserver.observe(parent2);
|
|
29862
30366
|
}
|
|
29863
30367
|
/**
|
|
29864
|
-
*
|
|
30368
|
+
* Toggle the settings panel open/closed
|
|
30369
|
+
* @param container - The graph container element
|
|
30370
|
+
*/
|
|
30371
|
+
toggleSettingsPanel(container) {
|
|
30372
|
+
const existing = container.querySelector(".yasgui-graph-settings-panel");
|
|
30373
|
+
if (existing) {
|
|
30374
|
+
existing.remove();
|
|
30375
|
+
this.settingsPanelOpen = false;
|
|
30376
|
+
this.removeClickOutsideHandler();
|
|
30377
|
+
} else {
|
|
30378
|
+
const panel = this.createSettingsPanel(container);
|
|
30379
|
+
container.appendChild(panel);
|
|
30380
|
+
this.settingsPanelOpen = true;
|
|
30381
|
+
this.setupClickOutsideHandler(container, panel);
|
|
30382
|
+
}
|
|
30383
|
+
}
|
|
30384
|
+
/**
|
|
30385
|
+
* Setup click-outside-to-close handler for settings panel
|
|
30386
|
+
* @param container - The graph container element
|
|
30387
|
+
* @param panel - The settings panel element
|
|
30388
|
+
*/
|
|
30389
|
+
setupClickOutsideHandler(container, panel) {
|
|
30390
|
+
this.removeClickOutsideHandler();
|
|
30391
|
+
this.clickOutsideHandler = (event) => {
|
|
30392
|
+
const target = event.target;
|
|
30393
|
+
if (!panel.contains(target) && !this.isSettingsButton(target)) {
|
|
30394
|
+
this.toggleSettingsPanel(container);
|
|
30395
|
+
}
|
|
30396
|
+
};
|
|
30397
|
+
setTimeout(() => {
|
|
30398
|
+
document.addEventListener("click", this.clickOutsideHandler);
|
|
30399
|
+
}, 100);
|
|
30400
|
+
}
|
|
30401
|
+
/**
|
|
30402
|
+
* Remove the click-outside handler
|
|
30403
|
+
*/
|
|
30404
|
+
removeClickOutsideHandler() {
|
|
30405
|
+
if (this.clickOutsideHandler) {
|
|
30406
|
+
document.removeEventListener("click", this.clickOutsideHandler);
|
|
30407
|
+
this.clickOutsideHandler = null;
|
|
30408
|
+
}
|
|
30409
|
+
}
|
|
30410
|
+
/**
|
|
30411
|
+
* Check if a node is the settings button or inside it
|
|
30412
|
+
* @param node - The node to check
|
|
30413
|
+
*/
|
|
30414
|
+
isSettingsButton(node) {
|
|
30415
|
+
let current = node;
|
|
30416
|
+
while (current) {
|
|
30417
|
+
if (current instanceof Element && current.classList.contains("yasgui-graph-settings-button")) {
|
|
30418
|
+
return true;
|
|
30419
|
+
}
|
|
30420
|
+
current = current.parentNode;
|
|
30421
|
+
}
|
|
30422
|
+
return false;
|
|
30423
|
+
}
|
|
30424
|
+
/**
|
|
30425
|
+
* Build and return the settings panel element
|
|
30426
|
+
* @param container - The graph container element (used to re-draw on change)
|
|
30427
|
+
*/
|
|
30428
|
+
createSettingsPanel(_container) {
|
|
30429
|
+
const panel = document.createElement("div");
|
|
30430
|
+
panel.className = "yasgui-graph-settings-panel";
|
|
30431
|
+
panel.setAttribute("role", "dialog");
|
|
30432
|
+
panel.setAttribute("aria-label", "Graph settings");
|
|
30433
|
+
const title = document.createElement("div");
|
|
30434
|
+
title.className = "yasgui-graph-settings-title";
|
|
30435
|
+
title.textContent = "Graph Settings";
|
|
30436
|
+
panel.appendChild(title);
|
|
30437
|
+
const addSection = (label) => {
|
|
30438
|
+
const h = document.createElement("div");
|
|
30439
|
+
h.className = "yasgui-graph-settings-section";
|
|
30440
|
+
h.textContent = label;
|
|
30441
|
+
panel.appendChild(h);
|
|
30442
|
+
};
|
|
30443
|
+
const addToggle = (label, checked, onChange) => {
|
|
30444
|
+
const row = document.createElement("label");
|
|
30445
|
+
row.className = "yasgui-graph-settings-row";
|
|
30446
|
+
const input = document.createElement("input");
|
|
30447
|
+
input.type = "checkbox";
|
|
30448
|
+
input.checked = checked;
|
|
30449
|
+
input.addEventListener("change", () => onChange(input.checked));
|
|
30450
|
+
const span = document.createElement("span");
|
|
30451
|
+
span.textContent = label;
|
|
30452
|
+
row.appendChild(input);
|
|
30453
|
+
row.appendChild(span);
|
|
30454
|
+
panel.appendChild(row);
|
|
30455
|
+
};
|
|
30456
|
+
const addSelect = (label, options, current, onChange) => {
|
|
30457
|
+
const row = document.createElement("div");
|
|
30458
|
+
row.className = "yasgui-graph-settings-row";
|
|
30459
|
+
const lbl = document.createElement("span");
|
|
30460
|
+
lbl.textContent = label;
|
|
30461
|
+
const sel = document.createElement("select");
|
|
30462
|
+
sel.className = "yasgui-graph-settings-select";
|
|
30463
|
+
options.forEach((o) => {
|
|
30464
|
+
const opt = document.createElement("option");
|
|
30465
|
+
opt.value = o.value;
|
|
30466
|
+
opt.textContent = o.label;
|
|
30467
|
+
if (o.value === current) opt.selected = true;
|
|
30468
|
+
sel.appendChild(opt);
|
|
30469
|
+
});
|
|
30470
|
+
sel.addEventListener("change", () => onChange(sel.value));
|
|
30471
|
+
row.appendChild(lbl);
|
|
30472
|
+
row.appendChild(sel);
|
|
30473
|
+
panel.appendChild(row);
|
|
30474
|
+
};
|
|
30475
|
+
const applyAndRedraw = () => {
|
|
30476
|
+
saveSettings(this.settings);
|
|
30477
|
+
this.draw();
|
|
30478
|
+
};
|
|
30479
|
+
addSection("Arrows");
|
|
30480
|
+
addSelect(
|
|
30481
|
+
"Style",
|
|
30482
|
+
[
|
|
30483
|
+
{ value: "curved", label: "Curved" },
|
|
30484
|
+
{ value: "straight", label: "Straight" }
|
|
30485
|
+
],
|
|
30486
|
+
this.settings.edgeStyle,
|
|
30487
|
+
(v) => {
|
|
30488
|
+
this.settings.edgeStyle = v;
|
|
30489
|
+
applyAndRedraw();
|
|
30490
|
+
}
|
|
30491
|
+
);
|
|
30492
|
+
addSection("Predicate display");
|
|
30493
|
+
addSelect(
|
|
30494
|
+
"Display",
|
|
30495
|
+
[
|
|
30496
|
+
{ value: "label", label: "Label (prefixed URI)" },
|
|
30497
|
+
{ value: "icon", label: "Icon / symbol" },
|
|
30498
|
+
{ value: "none", label: "Hidden" }
|
|
30499
|
+
],
|
|
30500
|
+
this.settings.predicateDisplay,
|
|
30501
|
+
(v) => {
|
|
30502
|
+
this.settings.predicateDisplay = v;
|
|
30503
|
+
applyAndRedraw();
|
|
30504
|
+
}
|
|
30505
|
+
);
|
|
30506
|
+
addSection("Compact mode");
|
|
30507
|
+
addToggle("Compact mode", this.settings.compactMode, (v) => {
|
|
30508
|
+
this.settings.compactMode = v;
|
|
30509
|
+
applyAndRedraw();
|
|
30510
|
+
});
|
|
30511
|
+
addSection("Display");
|
|
30512
|
+
addToggle("Show node labels", this.settings.showNodeLabels, (v) => {
|
|
30513
|
+
this.settings.showNodeLabels = v;
|
|
30514
|
+
applyAndRedraw();
|
|
30515
|
+
});
|
|
30516
|
+
addToggle("Enable physics", this.settings.physicsEnabled, (v) => {
|
|
30517
|
+
this.settings.physicsEnabled = v;
|
|
30518
|
+
applyAndRedraw();
|
|
30519
|
+
});
|
|
30520
|
+
addSelect(
|
|
30521
|
+
"Node size",
|
|
30522
|
+
[
|
|
30523
|
+
{ value: "small", label: "Small" },
|
|
30524
|
+
{ value: "medium", label: "Medium" },
|
|
30525
|
+
{ value: "large", label: "Large" }
|
|
30526
|
+
],
|
|
30527
|
+
this.settings.nodeSize,
|
|
30528
|
+
(v) => {
|
|
30529
|
+
this.settings.nodeSize = v;
|
|
30530
|
+
applyAndRedraw();
|
|
30531
|
+
}
|
|
30532
|
+
);
|
|
30533
|
+
return panel;
|
|
30534
|
+
}
|
|
30535
|
+
/**
|
|
30536
|
+
* Setup double-click handler for node expansion
|
|
30537
|
+
*/
|
|
30538
|
+
setupNodeExpansion() {
|
|
30539
|
+
if (!this.network) return;
|
|
30540
|
+
this.network.on("doubleClick", (params) => {
|
|
30541
|
+
if (params.nodes.length === 0) return;
|
|
30542
|
+
const nodeId = params.nodes[0];
|
|
30543
|
+
const node = this.nodesDataSet.get(nodeId);
|
|
30544
|
+
if (node && node.uri && !node.uri.startsWith("_:")) {
|
|
30545
|
+
this.expandNode(node.uri);
|
|
30546
|
+
}
|
|
30547
|
+
});
|
|
30548
|
+
}
|
|
30549
|
+
/**
|
|
30550
|
+
* Expand a node by executing a DESCRIBE query for the given URI and
|
|
30551
|
+
* merging the returned triples into the existing graph.
|
|
30552
|
+
* @param uri - URI of the node to expand
|
|
30553
|
+
*/
|
|
30554
|
+
async expandNode(uri) {
|
|
30555
|
+
if (!this.yasr.executeQuery) {
|
|
30556
|
+
console.warn("yasgui-graph-plugin: background query execution not available (yasr.executeQuery is not configured)");
|
|
30557
|
+
return;
|
|
30558
|
+
}
|
|
30559
|
+
if (!this.triples || !this.prefixMap) return;
|
|
30560
|
+
if (this.expansionAbortController) {
|
|
30561
|
+
this.expansionAbortController.abort();
|
|
30562
|
+
}
|
|
30563
|
+
const controller = new AbortController();
|
|
30564
|
+
this.expansionAbortController = controller;
|
|
30565
|
+
const nodeId = this.uriToNodeId.get(uri);
|
|
30566
|
+
let originalColor = void 0;
|
|
30567
|
+
let originalBorderWidth = void 0;
|
|
30568
|
+
if (nodeId !== void 0) {
|
|
30569
|
+
const node = this.nodesDataSet.get(nodeId);
|
|
30570
|
+
if (node) {
|
|
30571
|
+
originalColor = node.color;
|
|
30572
|
+
originalBorderWidth = node.borderWidth;
|
|
30573
|
+
}
|
|
30574
|
+
this.nodesDataSet.update({
|
|
30575
|
+
id: nodeId,
|
|
30576
|
+
borderWidth: LOADING_BORDER_WIDTH,
|
|
30577
|
+
color: typeof originalColor === "object" && originalColor !== null ? { ...originalColor, border: LOADING_BORDER_COLOR } : { border: LOADING_BORDER_COLOR, background: originalColor != null ? originalColor : void 0 }
|
|
30578
|
+
});
|
|
30579
|
+
}
|
|
30580
|
+
const restoreNode = (borderWidth) => {
|
|
30581
|
+
if (nodeId !== void 0) {
|
|
30582
|
+
this.nodesDataSet.update({ id: nodeId, borderWidth, color: originalColor });
|
|
30583
|
+
}
|
|
30584
|
+
};
|
|
30585
|
+
try {
|
|
30586
|
+
const response = await this.yasr.executeQuery(`DESCRIBE <${uri}>`, {
|
|
30587
|
+
acceptHeader: "application/sparql-results+json",
|
|
30588
|
+
signal: controller.signal
|
|
30589
|
+
});
|
|
30590
|
+
if (controller.signal.aborted) {
|
|
30591
|
+
restoreNode(originalBorderWidth != null ? originalBorderWidth : DEFAULT_BORDER_WIDTH);
|
|
30592
|
+
return;
|
|
30593
|
+
}
|
|
30594
|
+
const newTriples = await parseBackgroundQueryResponse(response);
|
|
30595
|
+
if (controller.signal.aborted) {
|
|
30596
|
+
restoreNode(originalBorderWidth != null ? originalBorderWidth : DEFAULT_BORDER_WIDTH);
|
|
30597
|
+
return;
|
|
30598
|
+
}
|
|
30599
|
+
const existingKeys = new Set(
|
|
30600
|
+
this.triples.map((t) => `${t.subject}|${t.predicate}|${t.object.value}`)
|
|
30601
|
+
);
|
|
30602
|
+
const uniqueNew = newTriples.filter(
|
|
30603
|
+
(t) => !existingKeys.has(`${t.subject}|${t.predicate}|${t.object.value}`)
|
|
30604
|
+
);
|
|
30605
|
+
if (uniqueNew.length > 0) {
|
|
30606
|
+
this.triples = [...this.triples, ...uniqueNew];
|
|
30607
|
+
this.mergeNewTriples();
|
|
30608
|
+
}
|
|
30609
|
+
restoreNode(EXPANDED_BORDER_WIDTH);
|
|
30610
|
+
} catch (error) {
|
|
30611
|
+
if ((error == null ? void 0 : error.name) === "AbortError") {
|
|
30612
|
+
restoreNode(originalBorderWidth != null ? originalBorderWidth : DEFAULT_BORDER_WIDTH);
|
|
30613
|
+
return;
|
|
30614
|
+
}
|
|
30615
|
+
console.error("yasgui-graph-plugin: error expanding node", uri, error);
|
|
30616
|
+
restoreNode(originalBorderWidth != null ? originalBorderWidth : DEFAULT_BORDER_WIDTH);
|
|
30617
|
+
}
|
|
30618
|
+
}
|
|
30619
|
+
/**
|
|
30620
|
+
* Incrementally add new triples to the vis-network DataSets without a full redraw.
|
|
30621
|
+
* New nodes and edges are added; existing ones are left untouched.
|
|
30622
|
+
* Expects `this.triples` to already include the new triples.
|
|
30623
|
+
*/
|
|
30624
|
+
mergeNewTriples() {
|
|
30625
|
+
if (!this.triples || !this.prefixMap || !this.nodesDataSet || !this.edgesDataSet) return;
|
|
30626
|
+
const themeColors = getThemeNodeColors(this.currentTheme);
|
|
30627
|
+
const { nodes, edges } = triplesToGraph(this.triples, this.prefixMap, themeColors, this.settings);
|
|
30628
|
+
const existingValues = new Set(this.nodesDataSet.get().map((n) => n.fullValue));
|
|
30629
|
+
const nodesToAdd = nodes.filter((n) => !existingValues.has(n.fullValue));
|
|
30630
|
+
const existingEdgeKeys = new Set(
|
|
30631
|
+
this.edgesDataSet.get().map((e) => `${e.from}|${e.predicate}|${e.to}`)
|
|
30632
|
+
);
|
|
30633
|
+
const edgesToAdd = edges.filter(
|
|
30634
|
+
(e) => !existingEdgeKeys.has(`${e.from}|${e.predicate}|${e.to}`)
|
|
30635
|
+
);
|
|
30636
|
+
if (nodesToAdd.length > 0) {
|
|
30637
|
+
this.nodesDataSet.add(nodesToAdd);
|
|
30638
|
+
nodesToAdd.forEach((n) => {
|
|
30639
|
+
if (n.uri != null) {
|
|
30640
|
+
this.uriToNodeId.set(n.uri, n.id);
|
|
30641
|
+
}
|
|
30642
|
+
});
|
|
30643
|
+
}
|
|
30644
|
+
if (edgesToAdd.length > 0) {
|
|
30645
|
+
this.edgesDataSet.add(edgesToAdd);
|
|
30646
|
+
}
|
|
30647
|
+
}
|
|
30648
|
+
/**
|
|
29865
30649
|
* @returns Icon element
|
|
29866
30650
|
*/
|
|
29867
30651
|
getIcon() {
|
|
@@ -29882,6 +30666,11 @@ var GraphPlugin = class {
|
|
|
29882
30666
|
* Cleanup when plugin is destroyed
|
|
29883
30667
|
*/
|
|
29884
30668
|
destroy() {
|
|
30669
|
+
this.removeClickOutsideHandler();
|
|
30670
|
+
if (this.expansionAbortController) {
|
|
30671
|
+
this.expansionAbortController.abort();
|
|
30672
|
+
this.expansionAbortController = null;
|
|
30673
|
+
}
|
|
29885
30674
|
if (this.themeObserver) {
|
|
29886
30675
|
this.themeObserver.disconnect();
|
|
29887
30676
|
this.themeObserver = null;
|