@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
|
@@ -67,14 +67,18 @@ function truncateLabel(text, maxLength = 50) {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
// src/networkConfig.ts
|
|
70
|
-
function getDefaultNetworkOptions(themeColors) {
|
|
70
|
+
function getDefaultNetworkOptions(themeColors, settings) {
|
|
71
|
+
const curved = !settings || settings.edgeStyle !== "straight";
|
|
72
|
+
const nodeSizeMap = { small: 6, medium: 10, large: 16 };
|
|
73
|
+
const nodeSize = (settings == null ? void 0 : settings.nodeSize) ? nodeSizeMap[settings.nodeSize] : 10;
|
|
74
|
+
const showNodeLabels = (settings == null ? void 0 : settings.showNodeLabels) !== false;
|
|
71
75
|
return {
|
|
72
76
|
// Configure canvas background color based on theme
|
|
73
77
|
configure: {
|
|
74
78
|
enabled: false
|
|
75
79
|
},
|
|
76
80
|
physics: {
|
|
77
|
-
enabled:
|
|
81
|
+
enabled: (settings == null ? void 0 : settings.physicsEnabled) !== false,
|
|
78
82
|
stabilization: {
|
|
79
83
|
enabled: true,
|
|
80
84
|
iterations: 200,
|
|
@@ -94,14 +98,16 @@ function getDefaultNetworkOptions(themeColors) {
|
|
|
94
98
|
dragView: true,
|
|
95
99
|
zoomView: true,
|
|
96
100
|
hover: true,
|
|
97
|
-
tooltipDelay: 300
|
|
101
|
+
tooltipDelay: 300,
|
|
98
102
|
// 300ms hover delay per spec
|
|
103
|
+
hideEdgesOnDrag: false,
|
|
104
|
+
hideEdgesOnZoom: false
|
|
99
105
|
},
|
|
100
106
|
nodes: {
|
|
101
107
|
shape: "dot",
|
|
102
|
-
size:
|
|
108
|
+
size: nodeSize,
|
|
103
109
|
font: {
|
|
104
|
-
size: 12,
|
|
110
|
+
size: showNodeLabels ? 12 : 0,
|
|
105
111
|
color: themeColors.text
|
|
106
112
|
},
|
|
107
113
|
borderWidth: 1,
|
|
@@ -116,7 +122,7 @@ function getDefaultNetworkOptions(themeColors) {
|
|
|
116
122
|
}
|
|
117
123
|
},
|
|
118
124
|
smooth: {
|
|
119
|
-
enabled:
|
|
125
|
+
enabled: curved,
|
|
120
126
|
type: "dynamic",
|
|
121
127
|
roundness: 0.5
|
|
122
128
|
},
|
|
@@ -136,6 +142,40 @@ function getDefaultNetworkOptions(themeColors) {
|
|
|
136
142
|
}
|
|
137
143
|
|
|
138
144
|
// src/parsers.ts
|
|
145
|
+
async function parseBackgroundQueryResponse(response) {
|
|
146
|
+
var _a, _b;
|
|
147
|
+
if (!response) return [];
|
|
148
|
+
try {
|
|
149
|
+
let data2;
|
|
150
|
+
if (typeof response.json === "function") {
|
|
151
|
+
data2 = await response.json();
|
|
152
|
+
} else if (typeof response.data === "string") {
|
|
153
|
+
data2 = JSON.parse(response.data);
|
|
154
|
+
} else if (typeof response === "object") {
|
|
155
|
+
data2 = response;
|
|
156
|
+
} else {
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
const bindings = (_b = (_a = data2 == null ? void 0 : data2.results) == null ? void 0 : _a.bindings) != null ? _b : [];
|
|
160
|
+
const triples = [];
|
|
161
|
+
for (const binding of bindings) {
|
|
162
|
+
if (!binding.subject || !binding.predicate || !binding.object || typeof binding.subject.value !== "string" || typeof binding.predicate.value !== "string" || typeof binding.object.value !== "string") continue;
|
|
163
|
+
triples.push({
|
|
164
|
+
subject: binding.subject.value,
|
|
165
|
+
predicate: binding.predicate.value,
|
|
166
|
+
object: {
|
|
167
|
+
value: binding.object.value,
|
|
168
|
+
type: binding.object.type || "uri",
|
|
169
|
+
datatype: binding.object.datatype,
|
|
170
|
+
lang: binding.object["xml:lang"]
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return triples;
|
|
175
|
+
} catch (e) {
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
}
|
|
139
179
|
function parseConstructResults(yasrResults) {
|
|
140
180
|
const triples = [];
|
|
141
181
|
if (!yasrResults || !yasrResults.getBindings) {
|
|
@@ -178,10 +218,129 @@ function getNodeColor(node, triples, themeColors) {
|
|
|
178
218
|
return themeColors.uri;
|
|
179
219
|
}
|
|
180
220
|
|
|
221
|
+
// src/predicateIcons.ts
|
|
222
|
+
var PREDICATE_ICONS = {
|
|
223
|
+
// RDF core
|
|
224
|
+
"http://www.w3.org/1999/02/22-rdf-syntax-ns#type": "a",
|
|
225
|
+
// Turtle's "a" shorthand
|
|
226
|
+
"http://www.w3.org/1999/02/22-rdf-syntax-ns#value": "val",
|
|
227
|
+
// RDFS
|
|
228
|
+
"http://www.w3.org/2000/01/rdf-schema#label": "lbl",
|
|
229
|
+
"http://www.w3.org/2000/01/rdf-schema#comment": "cmt",
|
|
230
|
+
"http://www.w3.org/2000/01/rdf-schema#subClassOf": "\u2282",
|
|
231
|
+
// ⊂
|
|
232
|
+
"http://www.w3.org/2000/01/rdf-schema#subPropertyOf": "\u2286",
|
|
233
|
+
// ⊆
|
|
234
|
+
"http://www.w3.org/2000/01/rdf-schema#domain": "dom",
|
|
235
|
+
"http://www.w3.org/2000/01/rdf-schema#range": "rng",
|
|
236
|
+
"http://www.w3.org/2000/01/rdf-schema#seeAlso": "see",
|
|
237
|
+
"http://www.w3.org/2000/01/rdf-schema#isDefinedBy": "idb",
|
|
238
|
+
// OWL
|
|
239
|
+
"http://www.w3.org/2002/07/owl#sameAs": "\u2261",
|
|
240
|
+
// ≡
|
|
241
|
+
"http://www.w3.org/2002/07/owl#equivalentClass": "\u2245",
|
|
242
|
+
// ≅
|
|
243
|
+
"http://www.w3.org/2002/07/owl#inverseOf": "\u21C4",
|
|
244
|
+
// ⇄
|
|
245
|
+
"http://www.w3.org/2002/07/owl#disjointWith": "\u2260",
|
|
246
|
+
// ≠
|
|
247
|
+
// SKOS
|
|
248
|
+
"http://www.w3.org/2004/02/skos/core#prefLabel": "\u2605",
|
|
249
|
+
// ★
|
|
250
|
+
"http://www.w3.org/2004/02/skos/core#altLabel": "\u2606",
|
|
251
|
+
// ☆
|
|
252
|
+
"http://www.w3.org/2004/02/skos/core#definition": "def",
|
|
253
|
+
"http://www.w3.org/2004/02/skos/core#broader": "\u2191",
|
|
254
|
+
// ↑
|
|
255
|
+
"http://www.w3.org/2004/02/skos/core#narrower": "\u2193",
|
|
256
|
+
// ↓
|
|
257
|
+
"http://www.w3.org/2004/02/skos/core#related": "\u2194",
|
|
258
|
+
// ↔
|
|
259
|
+
"http://www.w3.org/2004/02/skos/core#note": "note",
|
|
260
|
+
"http://www.w3.org/2004/02/skos/core#exactMatch": "\u2261",
|
|
261
|
+
// ≡
|
|
262
|
+
"http://www.w3.org/2004/02/skos/core#closeMatch": "\u2248",
|
|
263
|
+
// ≈
|
|
264
|
+
// Dublin Core Terms
|
|
265
|
+
"http://purl.org/dc/terms/title": "ttl",
|
|
266
|
+
"http://purl.org/dc/terms/description": "dsc",
|
|
267
|
+
"http://purl.org/dc/terms/created": "crt",
|
|
268
|
+
"http://purl.org/dc/terms/modified": "mod",
|
|
269
|
+
"http://purl.org/dc/terms/creator": "by",
|
|
270
|
+
"http://purl.org/dc/terms/subject": "sbj",
|
|
271
|
+
// FOAF
|
|
272
|
+
"http://xmlns.com/foaf/0.1/name": "nm",
|
|
273
|
+
"http://xmlns.com/foaf/0.1/knows": "\u27F7",
|
|
274
|
+
// ⟷
|
|
275
|
+
"http://xmlns.com/foaf/0.1/member": "mbr",
|
|
276
|
+
// Schema.org
|
|
277
|
+
"http://schema.org/name": "nm",
|
|
278
|
+
"http://schema.org/description": "dsc"
|
|
279
|
+
};
|
|
280
|
+
function getPredicateIcon(predicateUri) {
|
|
281
|
+
return PREDICATE_ICONS[predicateUri];
|
|
282
|
+
}
|
|
283
|
+
|
|
181
284
|
// src/transformers.ts
|
|
182
|
-
|
|
285
|
+
var RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
|
|
286
|
+
var SCHEMA_IMAGE = "https://schema.org/image";
|
|
287
|
+
var SCHEMA_ICON = "https://schema.org/icon";
|
|
288
|
+
var RDFS_LABEL = "http://www.w3.org/2000/01/rdf-schema#label";
|
|
289
|
+
var RDFS_SUBCLASSOF = "http://www.w3.org/2000/01/rdf-schema#subClassOf";
|
|
290
|
+
var SUPPRESSED_PREDICATES = /* @__PURE__ */ new Set([SCHEMA_IMAGE, SCHEMA_ICON, RDFS_LABEL]);
|
|
291
|
+
function appendTooltipRows(title, rows) {
|
|
292
|
+
const closingTag = "</div>";
|
|
293
|
+
const idx = title.lastIndexOf(closingTag);
|
|
294
|
+
if (idx === -1) return title + rows;
|
|
295
|
+
return title.slice(0, idx) + rows + closingTag;
|
|
296
|
+
}
|
|
297
|
+
function buildVisualTooltipRow(key, value) {
|
|
298
|
+
return `<div class="yasgui-tooltip-row"><span class="yasgui-tooltip-key">${escapeHtml(key)}</span><span class="yasgui-tooltip-val">${escapeHtml(value)}</span></div>`;
|
|
299
|
+
}
|
|
300
|
+
function escapeHtml(str) {
|
|
301
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
302
|
+
}
|
|
303
|
+
function createCompactNodeTooltipHTML(uri, triples, prefixMap) {
|
|
304
|
+
const isBlankNode = uri.startsWith("_:");
|
|
305
|
+
let rows = `<div class="yasgui-tooltip-type">${isBlankNode ? "Blank Node" : "URI"}</div>`;
|
|
306
|
+
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>`;
|
|
307
|
+
triples.filter((t) => t.subject === uri && t.predicate === RDF_TYPE).forEach((t) => {
|
|
308
|
+
const typeLabel = applyPrefix(t.object.value, prefixMap);
|
|
309
|
+
rows += `<div class="yasgui-tooltip-row"><span class="yasgui-tooltip-key">rdf:type</span><span class="yasgui-tooltip-val">${escapeHtml(typeLabel)}</span></div>`;
|
|
310
|
+
});
|
|
311
|
+
triples.filter((t) => t.subject === uri && t.object.type === "literal").forEach((t) => {
|
|
312
|
+
const predLabel = applyPrefix(t.predicate, prefixMap);
|
|
313
|
+
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>`;
|
|
314
|
+
});
|
|
315
|
+
return `<div class="yasgui-graph-tooltip">${rows}</div>`;
|
|
316
|
+
}
|
|
317
|
+
function createNodeTooltipHTML(nodeType, value, datatype, lang, prefixMap) {
|
|
318
|
+
const typeLabel = nodeType === "uri" ? "URI" : nodeType === "literal" ? "Literal" : "Blank Node";
|
|
319
|
+
let rows = `<div class="yasgui-tooltip-type">${typeLabel}</div>`;
|
|
320
|
+
if (nodeType === "literal") {
|
|
321
|
+
rows += `<div class="yasgui-tooltip-row"><span class="yasgui-tooltip-key">Value</span><span class="yasgui-tooltip-val">${escapeHtml(value)}</span></div>`;
|
|
322
|
+
if (datatype) {
|
|
323
|
+
const dtLabel = prefixMap ? applyPrefix(datatype, prefixMap) : datatype;
|
|
324
|
+
rows += `<div class="yasgui-tooltip-row"><span class="yasgui-tooltip-key">Datatype</span><span class="yasgui-tooltip-val">${escapeHtml(dtLabel)}</span></div>`;
|
|
325
|
+
}
|
|
326
|
+
if (lang) {
|
|
327
|
+
rows += `<div class="yasgui-tooltip-row"><span class="yasgui-tooltip-key">Language</span><span class="yasgui-tooltip-val">${escapeHtml(lang)}</span></div>`;
|
|
328
|
+
}
|
|
329
|
+
} else if (nodeType === "uri") {
|
|
330
|
+
rows += `<div class="yasgui-tooltip-row"><span class="yasgui-tooltip-key">Full URI</span><span class="yasgui-tooltip-val">${escapeHtml(value)}</span></div>`;
|
|
331
|
+
} else if (nodeType === "bnode") {
|
|
332
|
+
rows += `<div class="yasgui-tooltip-row"><span class="yasgui-tooltip-key">Identifier</span><span class="yasgui-tooltip-val">${escapeHtml(value)}</span></div>`;
|
|
333
|
+
}
|
|
334
|
+
return `<div class="yasgui-graph-tooltip">${rows}</div>`;
|
|
335
|
+
}
|
|
336
|
+
function createEdgeTooltipHTML(predicateUri) {
|
|
337
|
+
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>`;
|
|
338
|
+
return `<div class="yasgui-graph-tooltip">${rows}</div>`;
|
|
339
|
+
}
|
|
340
|
+
function createNodeMap(triples, prefixMap, themeColors, settings) {
|
|
183
341
|
const nodeMap = /* @__PURE__ */ new Map();
|
|
184
342
|
let nodeId = 1;
|
|
343
|
+
const sizeMultiplier = (settings == null ? void 0 : settings.nodeSize) === "small" ? 0.5 : (settings == null ? void 0 : settings.nodeSize) === "large" ? 2 : 1;
|
|
185
344
|
triples.forEach((triple) => {
|
|
186
345
|
if (!nodeMap.has(triple.subject)) {
|
|
187
346
|
const isBlankNode = triple.subject.startsWith("_:");
|
|
@@ -193,11 +352,18 @@ function createNodeMap(triples, prefixMap, themeColors) {
|
|
|
193
352
|
color: getNodeColor({ uri: triple.subject, type: "uri" }, triples, themeColors),
|
|
194
353
|
type: "uri",
|
|
195
354
|
fullValue: triple.subject,
|
|
196
|
-
|
|
355
|
+
size: 10 * sizeMultiplier,
|
|
356
|
+
title: createNodeTooltipHTML(
|
|
357
|
+
isBlankNode ? "bnode" : "uri",
|
|
358
|
+
triple.subject,
|
|
359
|
+
void 0,
|
|
360
|
+
void 0,
|
|
361
|
+
prefixMap
|
|
362
|
+
)
|
|
197
363
|
});
|
|
198
364
|
}
|
|
199
365
|
const objValue = triple.object.value;
|
|
200
|
-
if (!nodeMap.has(objValue)) {
|
|
366
|
+
if (!nodeMap.has(objValue) && !SUPPRESSED_PREDICATES.has(triple.predicate)) {
|
|
201
367
|
const isLiteral = triple.object.type === "literal";
|
|
202
368
|
const isBlankNode = !isLiteral && objValue.startsWith("_:");
|
|
203
369
|
let label;
|
|
@@ -206,15 +372,21 @@ function createNodeMap(triples, prefixMap, themeColors) {
|
|
|
206
372
|
if (isLiteral) {
|
|
207
373
|
label = truncateLabel(objValue);
|
|
208
374
|
fullValue = objValue;
|
|
209
|
-
title =
|
|
375
|
+
title = createNodeTooltipHTML(
|
|
376
|
+
"literal",
|
|
377
|
+
objValue,
|
|
378
|
+
triple.object.datatype,
|
|
379
|
+
triple.object.lang,
|
|
380
|
+
prefixMap
|
|
381
|
+
);
|
|
210
382
|
} else if (isBlankNode) {
|
|
211
383
|
label = objValue;
|
|
212
384
|
fullValue = objValue;
|
|
213
|
-
title = objValue;
|
|
385
|
+
title = createNodeTooltipHTML("bnode", objValue);
|
|
214
386
|
} else {
|
|
215
387
|
label = truncateLabel(applyPrefix(objValue, prefixMap));
|
|
216
388
|
fullValue = objValue;
|
|
217
|
-
title =
|
|
389
|
+
title = createNodeTooltipHTML("uri", objValue, void 0, void 0, prefixMap);
|
|
218
390
|
}
|
|
219
391
|
nodeMap.set(objValue, {
|
|
220
392
|
id: nodeId++,
|
|
@@ -227,39 +399,157 @@ function createNodeMap(triples, prefixMap, themeColors) {
|
|
|
227
399
|
),
|
|
228
400
|
type: isLiteral ? "literal" : "uri",
|
|
229
401
|
fullValue,
|
|
402
|
+
size: (isLiteral ? 5 : 10) * sizeMultiplier,
|
|
230
403
|
title
|
|
231
404
|
});
|
|
232
405
|
}
|
|
233
406
|
});
|
|
234
407
|
return nodeMap;
|
|
235
408
|
}
|
|
236
|
-
function createEdgesArray(triples, nodeMap, prefixMap) {
|
|
409
|
+
function createEdgesArray(triples, nodeMap, prefixMap, settings) {
|
|
237
410
|
const edges = [];
|
|
238
411
|
const edgeSet = /* @__PURE__ */ new Set();
|
|
239
412
|
triples.forEach((triple) => {
|
|
413
|
+
var _a, _b;
|
|
414
|
+
if (SUPPRESSED_PREDICATES.has(triple.predicate)) return;
|
|
240
415
|
const fromNode = nodeMap.get(triple.subject);
|
|
241
416
|
const toNode = nodeMap.get(triple.object.value);
|
|
242
417
|
if (!fromNode || !toNode) return;
|
|
243
418
|
const edgeKey = `${fromNode.id}-${triple.predicate}-${toNode.id}`;
|
|
244
419
|
if (!edgeSet.has(edgeKey)) {
|
|
245
420
|
edgeSet.add(edgeKey);
|
|
421
|
+
let edgeLabel;
|
|
422
|
+
const predicateDisplay = (_a = settings == null ? void 0 : settings.predicateDisplay) != null ? _a : "label";
|
|
423
|
+
if (predicateDisplay === "none") {
|
|
424
|
+
edgeLabel = "";
|
|
425
|
+
} else if (predicateDisplay === "icon") {
|
|
426
|
+
edgeLabel = (_b = getPredicateIcon(triple.predicate)) != null ? _b : truncateLabel(applyPrefix(triple.predicate, prefixMap));
|
|
427
|
+
} else {
|
|
428
|
+
edgeLabel = truncateLabel(applyPrefix(triple.predicate, prefixMap));
|
|
429
|
+
}
|
|
246
430
|
edges.push({
|
|
247
431
|
id: `edge_${fromNode.id}_${toNode.id}_${edges.length}`,
|
|
248
432
|
from: fromNode.id,
|
|
249
433
|
to: toNode.id,
|
|
250
|
-
label:
|
|
434
|
+
label: edgeLabel,
|
|
251
435
|
predicate: triple.predicate,
|
|
252
|
-
title:
|
|
436
|
+
title: createEdgeTooltipHTML(triple.predicate),
|
|
253
437
|
arrows: "to"
|
|
254
438
|
});
|
|
255
439
|
}
|
|
256
440
|
});
|
|
257
441
|
return edges;
|
|
258
442
|
}
|
|
259
|
-
function
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
443
|
+
function isNodeVisible(node, triples, settings) {
|
|
444
|
+
if (node.uri && node.uri.startsWith("_:")) {
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
if (!settings.compactMode) {
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
450
|
+
if (node.type === "literal") {
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
const isClass = triples.some(
|
|
454
|
+
(t) => t.predicate === "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" && t.object.value === node.uri
|
|
455
|
+
);
|
|
456
|
+
if (isClass) {
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
function triplesToGraph(triples, prefixMap, themeColors, settings) {
|
|
462
|
+
const nodeMap = createNodeMap(triples, prefixMap, themeColors, settings);
|
|
463
|
+
const sizeMultiplier = (settings == null ? void 0 : settings.nodeSize) === "small" ? 0.5 : (settings == null ? void 0 : settings.nodeSize) === "large" ? 2 : 1;
|
|
464
|
+
const tripleIndex = /* @__PURE__ */ new Map();
|
|
465
|
+
for (const t of triples) {
|
|
466
|
+
if (!tripleIndex.has(t.subject)) tripleIndex.set(t.subject, /* @__PURE__ */ new Map());
|
|
467
|
+
const sp = tripleIndex.get(t.subject);
|
|
468
|
+
if (!sp.has(t.predicate)) sp.set(t.predicate, []);
|
|
469
|
+
sp.get(t.predicate).push(t.object.value);
|
|
470
|
+
}
|
|
471
|
+
function getNodeVisualIdx(uri) {
|
|
472
|
+
const sp = tripleIndex.get(uri);
|
|
473
|
+
if (!sp) return {};
|
|
474
|
+
const icons = sp.get(SCHEMA_ICON);
|
|
475
|
+
if (icons == null ? void 0 : icons.length) return { icon: icons[0] };
|
|
476
|
+
const images = sp.get(SCHEMA_IMAGE);
|
|
477
|
+
if (images == null ? void 0 : images.length) return { image: images[0] };
|
|
478
|
+
return {};
|
|
479
|
+
}
|
|
480
|
+
function resolveCompactVisualIdx(uri) {
|
|
481
|
+
var _a, _b, _c, _d;
|
|
482
|
+
const own = getNodeVisualIdx(uri);
|
|
483
|
+
if (own.icon || own.image) return own;
|
|
484
|
+
const typeUris = (_b = (_a = tripleIndex.get(uri)) == null ? void 0 : _a.get(RDF_TYPE)) != null ? _b : [];
|
|
485
|
+
for (const typeUri of typeUris) {
|
|
486
|
+
const cv = getNodeVisualIdx(typeUri);
|
|
487
|
+
if (cv.icon || cv.image) return cv;
|
|
488
|
+
}
|
|
489
|
+
for (const typeUri of typeUris) {
|
|
490
|
+
const superUris = (_d = (_c = tripleIndex.get(typeUri)) == null ? void 0 : _c.get(RDFS_SUBCLASSOF)) != null ? _d : [];
|
|
491
|
+
for (const superUri of superUris) {
|
|
492
|
+
const sv = getNodeVisualIdx(superUri);
|
|
493
|
+
if (sv.icon || sv.image) return sv;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return {};
|
|
497
|
+
}
|
|
498
|
+
if (settings == null ? void 0 : settings.compactMode) {
|
|
499
|
+
const subjects = new Set(triples.map((t) => t.subject));
|
|
500
|
+
subjects.forEach((subjectUri) => {
|
|
501
|
+
const node = nodeMap.get(subjectUri);
|
|
502
|
+
if (node) {
|
|
503
|
+
node.title = createCompactNodeTooltipHTML(subjectUri, triples, prefixMap);
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
nodeMap.forEach((node) => {
|
|
508
|
+
var _a, _b;
|
|
509
|
+
if (!node.uri || node.type === "literal") return;
|
|
510
|
+
const visual = (settings == null ? void 0 : settings.compactMode) ? resolveCompactVisualIdx(node.uri) : getNodeVisualIdx(node.uri);
|
|
511
|
+
const rdfsLabel = (_b = (_a = tripleIndex.get(node.uri)) == null ? void 0 : _a.get(RDFS_LABEL)) == null ? void 0 : _b[0];
|
|
512
|
+
if (visual.icon) {
|
|
513
|
+
node.shape = "text";
|
|
514
|
+
node.label = rdfsLabel ? `${visual.icon}
|
|
515
|
+
${rdfsLabel}` : visual.icon;
|
|
516
|
+
node.font = { size: 14 * sizeMultiplier };
|
|
517
|
+
if (!(settings == null ? void 0 : settings.compactMode)) {
|
|
518
|
+
node.title = appendTooltipRows(node.title, buildVisualTooltipRow("Icon", visual.icon));
|
|
519
|
+
}
|
|
520
|
+
} else if (visual.image) {
|
|
521
|
+
let imageAllowed = false;
|
|
522
|
+
try {
|
|
523
|
+
const parsed = new URL(visual.image);
|
|
524
|
+
imageAllowed = parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
525
|
+
} catch (e) {
|
|
526
|
+
}
|
|
527
|
+
if (imageAllowed) {
|
|
528
|
+
node.shape = "circularImage";
|
|
529
|
+
node.image = visual.image;
|
|
530
|
+
}
|
|
531
|
+
if (rdfsLabel) {
|
|
532
|
+
node.label = rdfsLabel;
|
|
533
|
+
node.font = { size: 14 * sizeMultiplier };
|
|
534
|
+
}
|
|
535
|
+
if (!(settings == null ? void 0 : settings.compactMode)) {
|
|
536
|
+
node.title = appendTooltipRows(node.title, buildVisualTooltipRow("Image", visual.image));
|
|
537
|
+
}
|
|
538
|
+
} else if (rdfsLabel) {
|
|
539
|
+
node.label = rdfsLabel;
|
|
540
|
+
}
|
|
541
|
+
if (rdfsLabel && !(settings == null ? void 0 : settings.compactMode)) {
|
|
542
|
+
node.title = appendTooltipRows(node.title, buildVisualTooltipRow("rdfs:label", rdfsLabel));
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
const visibleNodeIds = /* @__PURE__ */ new Set();
|
|
546
|
+
nodeMap.forEach((node) => {
|
|
547
|
+
if (!settings || isNodeVisible(node, triples, settings)) {
|
|
548
|
+
visibleNodeIds.add(node.id);
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
const edges = createEdgesArray(triples, nodeMap, prefixMap, settings).filter((e) => visibleNodeIds.has(e.from) && visibleNodeIds.has(e.to));
|
|
552
|
+
const nodes = Array.from(nodeMap.values()).filter((n) => visibleNodeIds.has(n.id));
|
|
263
553
|
return { nodes, edges };
|
|
264
554
|
}
|
|
265
555
|
|
|
@@ -29730,9 +30020,44 @@ function watchThemeChanges(callback) {
|
|
|
29730
30020
|
return observer;
|
|
29731
30021
|
}
|
|
29732
30022
|
|
|
30023
|
+
// src/settings.ts
|
|
30024
|
+
var DEFAULT_SETTINGS = {
|
|
30025
|
+
edgeStyle: "curved",
|
|
30026
|
+
compactMode: false,
|
|
30027
|
+
predicateDisplay: "icon",
|
|
30028
|
+
showNodeLabels: true,
|
|
30029
|
+
physicsEnabled: true,
|
|
30030
|
+
nodeSize: "medium"
|
|
30031
|
+
};
|
|
30032
|
+
var STORAGE_KEY = "yasgui-graph-plugin-settings";
|
|
30033
|
+
function loadSettings() {
|
|
30034
|
+
try {
|
|
30035
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
30036
|
+
if (stored) {
|
|
30037
|
+
return { ...DEFAULT_SETTINGS, ...JSON.parse(stored) };
|
|
30038
|
+
}
|
|
30039
|
+
} catch (e) {
|
|
30040
|
+
}
|
|
30041
|
+
return { ...DEFAULT_SETTINGS };
|
|
30042
|
+
}
|
|
30043
|
+
function saveSettings(settings) {
|
|
30044
|
+
try {
|
|
30045
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
|
|
30046
|
+
} catch (e) {
|
|
30047
|
+
}
|
|
30048
|
+
}
|
|
30049
|
+
|
|
29733
30050
|
// src/GraphPlugin.ts
|
|
30051
|
+
var LOADING_BORDER_WIDTH = 4;
|
|
30052
|
+
var LOADING_BORDER_COLOR = "#ffa500";
|
|
30053
|
+
var EXPANDED_BORDER_WIDTH = 3;
|
|
30054
|
+
var DEFAULT_BORDER_WIDTH = 2;
|
|
29734
30055
|
var GraphPlugin = class {
|
|
29735
30056
|
constructor(yasr) {
|
|
30057
|
+
this.settingsPanelOpen = false;
|
|
30058
|
+
this.clickOutsideHandler = null;
|
|
30059
|
+
this.expansionAbortController = null;
|
|
30060
|
+
this.uriToNodeId = /* @__PURE__ */ new Map();
|
|
29736
30061
|
this.yasr = yasr;
|
|
29737
30062
|
this.network = null;
|
|
29738
30063
|
this.currentTheme = getCurrentTheme();
|
|
@@ -29742,6 +30067,7 @@ var GraphPlugin = class {
|
|
|
29742
30067
|
this.edgesDataSet = null;
|
|
29743
30068
|
this.triples = null;
|
|
29744
30069
|
this.prefixMap = null;
|
|
30070
|
+
this.settings = loadSettings();
|
|
29745
30071
|
}
|
|
29746
30072
|
/**
|
|
29747
30073
|
* Plugin priority (higher = shown first in tabs)
|
|
@@ -29755,6 +30081,12 @@ var GraphPlugin = class {
|
|
|
29755
30081
|
static get label() {
|
|
29756
30082
|
return "Graph";
|
|
29757
30083
|
}
|
|
30084
|
+
/**
|
|
30085
|
+
* Help/documentation URL
|
|
30086
|
+
*/
|
|
30087
|
+
static get helpReference() {
|
|
30088
|
+
return "https://yasgui-doc.matdata.eu/docs/user-guide#graph-plugin";
|
|
30089
|
+
}
|
|
29758
30090
|
/**
|
|
29759
30091
|
* Check if plugin can handle the current results
|
|
29760
30092
|
* @returns True if results are from CONSTRUCT or DESCRIBE query
|
|
@@ -29775,6 +30107,13 @@ var GraphPlugin = class {
|
|
|
29775
30107
|
* Render the graph visualization
|
|
29776
30108
|
*/
|
|
29777
30109
|
draw() {
|
|
30110
|
+
const wasPanelOpen = this.settingsPanelOpen;
|
|
30111
|
+
if (this.expansionAbortController) {
|
|
30112
|
+
this.expansionAbortController.abort();
|
|
30113
|
+
this.expansionAbortController = null;
|
|
30114
|
+
}
|
|
30115
|
+
this.expandedNodes = /* @__PURE__ */ new Set();
|
|
30116
|
+
this.uriToNodeId = /* @__PURE__ */ new Map();
|
|
29778
30117
|
this.yasr.resultsEl.innerHTML = "";
|
|
29779
30118
|
try {
|
|
29780
30119
|
this.triples = parseConstructResults(this.yasr.results);
|
|
@@ -29791,24 +30130,52 @@ var GraphPlugin = class {
|
|
|
29791
30130
|
this.prefixMap = extractPrefixes(this.yasr);
|
|
29792
30131
|
this.currentTheme = getCurrentTheme();
|
|
29793
30132
|
const themeColors = getThemeNodeColors(this.currentTheme);
|
|
29794
|
-
const { nodes, edges } = triplesToGraph(this.triples, this.prefixMap, themeColors);
|
|
30133
|
+
const { nodes, edges } = triplesToGraph(this.triples, this.prefixMap, themeColors, this.settings);
|
|
29795
30134
|
const container = document.createElement("div");
|
|
29796
30135
|
container.className = "yasgui-graph-plugin-container";
|
|
29797
30136
|
container.id = "yasgui-graph-plugin-container";
|
|
29798
30137
|
this.yasr.resultsEl.appendChild(container);
|
|
29799
30138
|
this.nodesDataSet = new DataSet(nodes);
|
|
29800
30139
|
this.edgesDataSet = new DataSet(edges);
|
|
29801
|
-
const options = getDefaultNetworkOptions(themeColors);
|
|
30140
|
+
const options = getDefaultNetworkOptions(themeColors, this.settings);
|
|
29802
30141
|
this.network = new Network(
|
|
29803
30142
|
container,
|
|
29804
30143
|
{ nodes: this.nodesDataSet, edges: this.edgesDataSet },
|
|
29805
30144
|
options
|
|
29806
30145
|
);
|
|
30146
|
+
this.setupHtmlTooltips(container);
|
|
29807
30147
|
this.applyCanvasBackground(themeColors.background);
|
|
29808
|
-
this.
|
|
29809
|
-
|
|
29810
|
-
this.network.
|
|
29811
|
-
|
|
30148
|
+
this.setupContainerResize(container);
|
|
30149
|
+
if (this.settings.physicsEnabled) {
|
|
30150
|
+
this.network.on("stabilizationIterationsDone", () => {
|
|
30151
|
+
this.network.setOptions({ physics: { enabled: true } });
|
|
30152
|
+
this.network.fit({ maxZoomLevel: 3 });
|
|
30153
|
+
});
|
|
30154
|
+
} else {
|
|
30155
|
+
setTimeout(() => {
|
|
30156
|
+
if (this.network) {
|
|
30157
|
+
this.network.fit({ maxZoomLevel: 3 });
|
|
30158
|
+
}
|
|
30159
|
+
}, 100);
|
|
30160
|
+
}
|
|
30161
|
+
this.network.on("dragEnd", (params) => {
|
|
30162
|
+
if (params.nodes.length > 0) {
|
|
30163
|
+
const positions = this.network.getPositions(params.nodes);
|
|
30164
|
+
const updates = params.nodes.map((id2) => ({
|
|
30165
|
+
id: id2,
|
|
30166
|
+
x: positions[id2].x,
|
|
30167
|
+
y: positions[id2].y,
|
|
30168
|
+
fixed: { x: true, y: true }
|
|
30169
|
+
}));
|
|
30170
|
+
this.nodesDataSet.update(updates);
|
|
30171
|
+
}
|
|
30172
|
+
});
|
|
30173
|
+
this.setupNodeExpansion();
|
|
30174
|
+
this.uriToNodeId = /* @__PURE__ */ new Map();
|
|
30175
|
+
this.nodesDataSet.get().forEach((node) => {
|
|
30176
|
+
if (node.uri) {
|
|
30177
|
+
this.uriToNodeId.set(node.uri, node.id);
|
|
30178
|
+
}
|
|
29812
30179
|
});
|
|
29813
30180
|
if (!this.themeObserver) {
|
|
29814
30181
|
this.themeObserver = watchThemeChanges((newTheme) => {
|
|
@@ -29827,6 +30194,20 @@ var GraphPlugin = class {
|
|
|
29827
30194
|
}
|
|
29828
30195
|
};
|
|
29829
30196
|
controls.appendChild(fitButton);
|
|
30197
|
+
const settingsButton = document.createElement("button");
|
|
30198
|
+
settingsButton.className = "yasgui-graph-button yasgui-graph-settings-button";
|
|
30199
|
+
settingsButton.setAttribute("aria-label", "Graph settings");
|
|
30200
|
+
settingsButton.innerHTML = `<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
30201
|
+
<path d="M8 10.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z"/>
|
|
30202
|
+
<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"/>
|
|
30203
|
+
</svg> Settings`;
|
|
30204
|
+
settingsButton.onclick = () => {
|
|
30205
|
+
this.toggleSettingsPanel(container);
|
|
30206
|
+
};
|
|
30207
|
+
controls.appendChild(settingsButton);
|
|
30208
|
+
if (wasPanelOpen) {
|
|
30209
|
+
this.toggleSettingsPanel(container);
|
|
30210
|
+
}
|
|
29830
30211
|
} catch (error) {
|
|
29831
30212
|
console.error("Error rendering graph:", error);
|
|
29832
30213
|
const errorDiv = document.createElement("div");
|
|
@@ -29835,6 +30216,111 @@ var GraphPlugin = class {
|
|
|
29835
30216
|
this.yasr.resultsEl.appendChild(errorDiv);
|
|
29836
30217
|
}
|
|
29837
30218
|
}
|
|
30219
|
+
/**
|
|
30220
|
+
* Setup custom HTML tooltip rendering for vis-network
|
|
30221
|
+
* @param container - The graph container element
|
|
30222
|
+
*/
|
|
30223
|
+
setupHtmlTooltips(container) {
|
|
30224
|
+
if (!this.network) return;
|
|
30225
|
+
let hideTimeout = null;
|
|
30226
|
+
this.network.on("hoverNode", (params) => {
|
|
30227
|
+
if (hideTimeout) {
|
|
30228
|
+
clearTimeout(hideTimeout);
|
|
30229
|
+
hideTimeout = null;
|
|
30230
|
+
}
|
|
30231
|
+
const nodeId = params.node;
|
|
30232
|
+
const node = this.nodesDataSet.get(nodeId);
|
|
30233
|
+
if (node && node.title) {
|
|
30234
|
+
this.showHtmlTooltip(container, node.title, params.pointer.DOM);
|
|
30235
|
+
}
|
|
30236
|
+
});
|
|
30237
|
+
this.network.on("hoverEdge", (params) => {
|
|
30238
|
+
if (hideTimeout) {
|
|
30239
|
+
clearTimeout(hideTimeout);
|
|
30240
|
+
hideTimeout = null;
|
|
30241
|
+
}
|
|
30242
|
+
const edgeId = params.edge;
|
|
30243
|
+
const edge = this.edgesDataSet.get(edgeId);
|
|
30244
|
+
if (edge && edge.title) {
|
|
30245
|
+
this.showHtmlTooltip(container, edge.title, params.pointer.DOM);
|
|
30246
|
+
}
|
|
30247
|
+
});
|
|
30248
|
+
this.network.on("blurNode", () => {
|
|
30249
|
+
hideTimeout = window.setTimeout(() => {
|
|
30250
|
+
this.hideHtmlTooltipIfNotHovered(container);
|
|
30251
|
+
}, 200);
|
|
30252
|
+
});
|
|
30253
|
+
this.network.on("blurEdge", () => {
|
|
30254
|
+
hideTimeout = window.setTimeout(() => {
|
|
30255
|
+
this.hideHtmlTooltipIfNotHovered(container);
|
|
30256
|
+
}, 200);
|
|
30257
|
+
});
|
|
30258
|
+
this.network.on("dragStart", () => {
|
|
30259
|
+
if (hideTimeout) {
|
|
30260
|
+
clearTimeout(hideTimeout);
|
|
30261
|
+
hideTimeout = null;
|
|
30262
|
+
}
|
|
30263
|
+
this.hideHtmlTooltip(container);
|
|
30264
|
+
});
|
|
30265
|
+
this.network.on("zoom", () => {
|
|
30266
|
+
if (hideTimeout) {
|
|
30267
|
+
clearTimeout(hideTimeout);
|
|
30268
|
+
hideTimeout = null;
|
|
30269
|
+
}
|
|
30270
|
+
this.hideHtmlTooltip(container);
|
|
30271
|
+
});
|
|
30272
|
+
}
|
|
30273
|
+
/**
|
|
30274
|
+
* Show HTML tooltip at specified position
|
|
30275
|
+
* @param container - Container element
|
|
30276
|
+
* @param htmlContent - HTML content to display
|
|
30277
|
+
* @param position - Mouse position {x, y}
|
|
30278
|
+
*/
|
|
30279
|
+
showHtmlTooltip(container, htmlContent, position) {
|
|
30280
|
+
this.hideHtmlTooltip(container);
|
|
30281
|
+
const tooltip = document.createElement("div");
|
|
30282
|
+
tooltip.className = "yasgui-graph-tooltip-container";
|
|
30283
|
+
tooltip.innerHTML = htmlContent;
|
|
30284
|
+
tooltip.style.position = "absolute";
|
|
30285
|
+
tooltip.style.left = `${position.x + 10}px`;
|
|
30286
|
+
tooltip.style.top = `${position.y + 10}px`;
|
|
30287
|
+
tooltip.style.zIndex = "1000";
|
|
30288
|
+
tooltip.addEventListener("mouseleave", () => {
|
|
30289
|
+
this.hideHtmlTooltip(container);
|
|
30290
|
+
});
|
|
30291
|
+
container.appendChild(tooltip);
|
|
30292
|
+
const rect = tooltip.getBoundingClientRect();
|
|
30293
|
+
const containerRect = container.getBoundingClientRect();
|
|
30294
|
+
if (rect.right > containerRect.right) {
|
|
30295
|
+
tooltip.style.left = `${position.x - rect.width - 10}px`;
|
|
30296
|
+
}
|
|
30297
|
+
if (rect.bottom > containerRect.bottom) {
|
|
30298
|
+
tooltip.style.top = `${position.y - rect.height - 10}px`;
|
|
30299
|
+
}
|
|
30300
|
+
}
|
|
30301
|
+
/**
|
|
30302
|
+
* Hide HTML tooltip
|
|
30303
|
+
* @param container - Container element
|
|
30304
|
+
*/
|
|
30305
|
+
hideHtmlTooltip(container) {
|
|
30306
|
+
const existingTooltip = container.querySelector(".yasgui-graph-tooltip-container");
|
|
30307
|
+
if (existingTooltip) {
|
|
30308
|
+
existingTooltip.remove();
|
|
30309
|
+
}
|
|
30310
|
+
}
|
|
30311
|
+
/**
|
|
30312
|
+
* Hide HTML tooltip only if mouse is not hovering over it
|
|
30313
|
+
* @param container - Container element
|
|
30314
|
+
*/
|
|
30315
|
+
hideHtmlTooltipIfNotHovered(container) {
|
|
30316
|
+
const existingTooltip = container.querySelector(".yasgui-graph-tooltip-container");
|
|
30317
|
+
if (existingTooltip) {
|
|
30318
|
+
const isHovered = existingTooltip.matches(":hover");
|
|
30319
|
+
if (!isHovered) {
|
|
30320
|
+
existingTooltip.remove();
|
|
30321
|
+
}
|
|
30322
|
+
}
|
|
30323
|
+
}
|
|
29838
30324
|
/**
|
|
29839
30325
|
* Apply theme to existing network
|
|
29840
30326
|
* @param newTheme - 'light' or 'dark'
|
|
@@ -29845,12 +30331,30 @@ var GraphPlugin = class {
|
|
|
29845
30331
|
}
|
|
29846
30332
|
this.currentTheme = newTheme;
|
|
29847
30333
|
const themeColors = getThemeNodeColors(newTheme);
|
|
29848
|
-
const
|
|
30334
|
+
const fixedNodes = {};
|
|
30335
|
+
this.nodesDataSet.get().forEach((node) => {
|
|
30336
|
+
var _a, _b;
|
|
30337
|
+
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);
|
|
30338
|
+
if (isFixed && node.x !== void 0 && node.y !== void 0) {
|
|
30339
|
+
fixedNodes[node.id] = { x: node.x, y: node.y };
|
|
30340
|
+
}
|
|
30341
|
+
});
|
|
30342
|
+
const { nodes, edges } = triplesToGraph(this.triples, this.prefixMap, themeColors, this.settings);
|
|
29849
30343
|
this.nodesDataSet.clear();
|
|
29850
30344
|
this.nodesDataSet.add(nodes);
|
|
29851
30345
|
this.edgesDataSet.clear();
|
|
29852
30346
|
this.edgesDataSet.add(edges);
|
|
29853
|
-
const
|
|
30347
|
+
const fixedIds = Object.keys(fixedNodes);
|
|
30348
|
+
if (fixedIds.length > 0) {
|
|
30349
|
+
const updates = fixedIds.map((id2) => ({
|
|
30350
|
+
id: id2,
|
|
30351
|
+
x: fixedNodes[id2].x,
|
|
30352
|
+
y: fixedNodes[id2].y,
|
|
30353
|
+
fixed: { x: true, y: true }
|
|
30354
|
+
}));
|
|
30355
|
+
this.nodesDataSet.update(updates);
|
|
30356
|
+
}
|
|
30357
|
+
const options = getDefaultNetworkOptions(themeColors, this.settings);
|
|
29854
30358
|
this.network.setOptions(options);
|
|
29855
30359
|
this.applyCanvasBackground(themeColors.background);
|
|
29856
30360
|
}
|
|
@@ -29887,7 +30391,287 @@ var GraphPlugin = class {
|
|
|
29887
30391
|
this.resizeObserver.observe(parent2);
|
|
29888
30392
|
}
|
|
29889
30393
|
/**
|
|
29890
|
-
*
|
|
30394
|
+
* Toggle the settings panel open/closed
|
|
30395
|
+
* @param container - The graph container element
|
|
30396
|
+
*/
|
|
30397
|
+
toggleSettingsPanel(container) {
|
|
30398
|
+
const existing = container.querySelector(".yasgui-graph-settings-panel");
|
|
30399
|
+
if (existing) {
|
|
30400
|
+
existing.remove();
|
|
30401
|
+
this.settingsPanelOpen = false;
|
|
30402
|
+
this.removeClickOutsideHandler();
|
|
30403
|
+
} else {
|
|
30404
|
+
const panel = this.createSettingsPanel(container);
|
|
30405
|
+
container.appendChild(panel);
|
|
30406
|
+
this.settingsPanelOpen = true;
|
|
30407
|
+
this.setupClickOutsideHandler(container, panel);
|
|
30408
|
+
}
|
|
30409
|
+
}
|
|
30410
|
+
/**
|
|
30411
|
+
* Setup click-outside-to-close handler for settings panel
|
|
30412
|
+
* @param container - The graph container element
|
|
30413
|
+
* @param panel - The settings panel element
|
|
30414
|
+
*/
|
|
30415
|
+
setupClickOutsideHandler(container, panel) {
|
|
30416
|
+
this.removeClickOutsideHandler();
|
|
30417
|
+
this.clickOutsideHandler = (event) => {
|
|
30418
|
+
const target = event.target;
|
|
30419
|
+
if (!panel.contains(target) && !this.isSettingsButton(target)) {
|
|
30420
|
+
this.toggleSettingsPanel(container);
|
|
30421
|
+
}
|
|
30422
|
+
};
|
|
30423
|
+
setTimeout(() => {
|
|
30424
|
+
document.addEventListener("click", this.clickOutsideHandler);
|
|
30425
|
+
}, 100);
|
|
30426
|
+
}
|
|
30427
|
+
/**
|
|
30428
|
+
* Remove the click-outside handler
|
|
30429
|
+
*/
|
|
30430
|
+
removeClickOutsideHandler() {
|
|
30431
|
+
if (this.clickOutsideHandler) {
|
|
30432
|
+
document.removeEventListener("click", this.clickOutsideHandler);
|
|
30433
|
+
this.clickOutsideHandler = null;
|
|
30434
|
+
}
|
|
30435
|
+
}
|
|
30436
|
+
/**
|
|
30437
|
+
* Check if a node is the settings button or inside it
|
|
30438
|
+
* @param node - The node to check
|
|
30439
|
+
*/
|
|
30440
|
+
isSettingsButton(node) {
|
|
30441
|
+
let current = node;
|
|
30442
|
+
while (current) {
|
|
30443
|
+
if (current instanceof Element && current.classList.contains("yasgui-graph-settings-button")) {
|
|
30444
|
+
return true;
|
|
30445
|
+
}
|
|
30446
|
+
current = current.parentNode;
|
|
30447
|
+
}
|
|
30448
|
+
return false;
|
|
30449
|
+
}
|
|
30450
|
+
/**
|
|
30451
|
+
* Build and return the settings panel element
|
|
30452
|
+
* @param container - The graph container element (used to re-draw on change)
|
|
30453
|
+
*/
|
|
30454
|
+
createSettingsPanel(_container) {
|
|
30455
|
+
const panel = document.createElement("div");
|
|
30456
|
+
panel.className = "yasgui-graph-settings-panel";
|
|
30457
|
+
panel.setAttribute("role", "dialog");
|
|
30458
|
+
panel.setAttribute("aria-label", "Graph settings");
|
|
30459
|
+
const title = document.createElement("div");
|
|
30460
|
+
title.className = "yasgui-graph-settings-title";
|
|
30461
|
+
title.textContent = "Graph Settings";
|
|
30462
|
+
panel.appendChild(title);
|
|
30463
|
+
const addSection = (label) => {
|
|
30464
|
+
const h = document.createElement("div");
|
|
30465
|
+
h.className = "yasgui-graph-settings-section";
|
|
30466
|
+
h.textContent = label;
|
|
30467
|
+
panel.appendChild(h);
|
|
30468
|
+
};
|
|
30469
|
+
const addToggle = (label, checked, onChange) => {
|
|
30470
|
+
const row = document.createElement("label");
|
|
30471
|
+
row.className = "yasgui-graph-settings-row";
|
|
30472
|
+
const input = document.createElement("input");
|
|
30473
|
+
input.type = "checkbox";
|
|
30474
|
+
input.checked = checked;
|
|
30475
|
+
input.addEventListener("change", () => onChange(input.checked));
|
|
30476
|
+
const span = document.createElement("span");
|
|
30477
|
+
span.textContent = label;
|
|
30478
|
+
row.appendChild(input);
|
|
30479
|
+
row.appendChild(span);
|
|
30480
|
+
panel.appendChild(row);
|
|
30481
|
+
};
|
|
30482
|
+
const addSelect = (label, options, current, onChange) => {
|
|
30483
|
+
const row = document.createElement("div");
|
|
30484
|
+
row.className = "yasgui-graph-settings-row";
|
|
30485
|
+
const lbl = document.createElement("span");
|
|
30486
|
+
lbl.textContent = label;
|
|
30487
|
+
const sel = document.createElement("select");
|
|
30488
|
+
sel.className = "yasgui-graph-settings-select";
|
|
30489
|
+
options.forEach((o) => {
|
|
30490
|
+
const opt = document.createElement("option");
|
|
30491
|
+
opt.value = o.value;
|
|
30492
|
+
opt.textContent = o.label;
|
|
30493
|
+
if (o.value === current) opt.selected = true;
|
|
30494
|
+
sel.appendChild(opt);
|
|
30495
|
+
});
|
|
30496
|
+
sel.addEventListener("change", () => onChange(sel.value));
|
|
30497
|
+
row.appendChild(lbl);
|
|
30498
|
+
row.appendChild(sel);
|
|
30499
|
+
panel.appendChild(row);
|
|
30500
|
+
};
|
|
30501
|
+
const applyAndRedraw = () => {
|
|
30502
|
+
saveSettings(this.settings);
|
|
30503
|
+
this.draw();
|
|
30504
|
+
};
|
|
30505
|
+
addSection("Arrows");
|
|
30506
|
+
addSelect(
|
|
30507
|
+
"Style",
|
|
30508
|
+
[
|
|
30509
|
+
{ value: "curved", label: "Curved" },
|
|
30510
|
+
{ value: "straight", label: "Straight" }
|
|
30511
|
+
],
|
|
30512
|
+
this.settings.edgeStyle,
|
|
30513
|
+
(v) => {
|
|
30514
|
+
this.settings.edgeStyle = v;
|
|
30515
|
+
applyAndRedraw();
|
|
30516
|
+
}
|
|
30517
|
+
);
|
|
30518
|
+
addSection("Predicate display");
|
|
30519
|
+
addSelect(
|
|
30520
|
+
"Display",
|
|
30521
|
+
[
|
|
30522
|
+
{ value: "label", label: "Label (prefixed URI)" },
|
|
30523
|
+
{ value: "icon", label: "Icon / symbol" },
|
|
30524
|
+
{ value: "none", label: "Hidden" }
|
|
30525
|
+
],
|
|
30526
|
+
this.settings.predicateDisplay,
|
|
30527
|
+
(v) => {
|
|
30528
|
+
this.settings.predicateDisplay = v;
|
|
30529
|
+
applyAndRedraw();
|
|
30530
|
+
}
|
|
30531
|
+
);
|
|
30532
|
+
addSection("Compact mode");
|
|
30533
|
+
addToggle("Compact mode", this.settings.compactMode, (v) => {
|
|
30534
|
+
this.settings.compactMode = v;
|
|
30535
|
+
applyAndRedraw();
|
|
30536
|
+
});
|
|
30537
|
+
addSection("Display");
|
|
30538
|
+
addToggle("Show node labels", this.settings.showNodeLabels, (v) => {
|
|
30539
|
+
this.settings.showNodeLabels = v;
|
|
30540
|
+
applyAndRedraw();
|
|
30541
|
+
});
|
|
30542
|
+
addToggle("Enable physics", this.settings.physicsEnabled, (v) => {
|
|
30543
|
+
this.settings.physicsEnabled = v;
|
|
30544
|
+
applyAndRedraw();
|
|
30545
|
+
});
|
|
30546
|
+
addSelect(
|
|
30547
|
+
"Node size",
|
|
30548
|
+
[
|
|
30549
|
+
{ value: "small", label: "Small" },
|
|
30550
|
+
{ value: "medium", label: "Medium" },
|
|
30551
|
+
{ value: "large", label: "Large" }
|
|
30552
|
+
],
|
|
30553
|
+
this.settings.nodeSize,
|
|
30554
|
+
(v) => {
|
|
30555
|
+
this.settings.nodeSize = v;
|
|
30556
|
+
applyAndRedraw();
|
|
30557
|
+
}
|
|
30558
|
+
);
|
|
30559
|
+
return panel;
|
|
30560
|
+
}
|
|
30561
|
+
/**
|
|
30562
|
+
* Setup double-click handler for node expansion
|
|
30563
|
+
*/
|
|
30564
|
+
setupNodeExpansion() {
|
|
30565
|
+
if (!this.network) return;
|
|
30566
|
+
this.network.on("doubleClick", (params) => {
|
|
30567
|
+
if (params.nodes.length === 0) return;
|
|
30568
|
+
const nodeId = params.nodes[0];
|
|
30569
|
+
const node = this.nodesDataSet.get(nodeId);
|
|
30570
|
+
if (node && node.uri && !node.uri.startsWith("_:")) {
|
|
30571
|
+
this.expandNode(node.uri);
|
|
30572
|
+
}
|
|
30573
|
+
});
|
|
30574
|
+
}
|
|
30575
|
+
/**
|
|
30576
|
+
* Expand a node by executing a DESCRIBE query for the given URI and
|
|
30577
|
+
* merging the returned triples into the existing graph.
|
|
30578
|
+
* @param uri - URI of the node to expand
|
|
30579
|
+
*/
|
|
30580
|
+
async expandNode(uri) {
|
|
30581
|
+
if (!this.yasr.executeQuery) {
|
|
30582
|
+
console.warn("yasgui-graph-plugin: background query execution not available (yasr.executeQuery is not configured)");
|
|
30583
|
+
return;
|
|
30584
|
+
}
|
|
30585
|
+
if (!this.triples || !this.prefixMap) return;
|
|
30586
|
+
if (this.expansionAbortController) {
|
|
30587
|
+
this.expansionAbortController.abort();
|
|
30588
|
+
}
|
|
30589
|
+
const controller = new AbortController();
|
|
30590
|
+
this.expansionAbortController = controller;
|
|
30591
|
+
const nodeId = this.uriToNodeId.get(uri);
|
|
30592
|
+
let originalColor = void 0;
|
|
30593
|
+
let originalBorderWidth = void 0;
|
|
30594
|
+
if (nodeId !== void 0) {
|
|
30595
|
+
const node = this.nodesDataSet.get(nodeId);
|
|
30596
|
+
if (node) {
|
|
30597
|
+
originalColor = node.color;
|
|
30598
|
+
originalBorderWidth = node.borderWidth;
|
|
30599
|
+
}
|
|
30600
|
+
this.nodesDataSet.update({
|
|
30601
|
+
id: nodeId,
|
|
30602
|
+
borderWidth: LOADING_BORDER_WIDTH,
|
|
30603
|
+
color: typeof originalColor === "object" && originalColor !== null ? { ...originalColor, border: LOADING_BORDER_COLOR } : { border: LOADING_BORDER_COLOR, background: originalColor != null ? originalColor : void 0 }
|
|
30604
|
+
});
|
|
30605
|
+
}
|
|
30606
|
+
const restoreNode = (borderWidth) => {
|
|
30607
|
+
if (nodeId !== void 0) {
|
|
30608
|
+
this.nodesDataSet.update({ id: nodeId, borderWidth, color: originalColor });
|
|
30609
|
+
}
|
|
30610
|
+
};
|
|
30611
|
+
try {
|
|
30612
|
+
const response = await this.yasr.executeQuery(`DESCRIBE <${uri}>`, {
|
|
30613
|
+
acceptHeader: "application/sparql-results+json",
|
|
30614
|
+
signal: controller.signal
|
|
30615
|
+
});
|
|
30616
|
+
if (controller.signal.aborted) {
|
|
30617
|
+
restoreNode(originalBorderWidth != null ? originalBorderWidth : DEFAULT_BORDER_WIDTH);
|
|
30618
|
+
return;
|
|
30619
|
+
}
|
|
30620
|
+
const newTriples = await parseBackgroundQueryResponse(response);
|
|
30621
|
+
if (controller.signal.aborted) {
|
|
30622
|
+
restoreNode(originalBorderWidth != null ? originalBorderWidth : DEFAULT_BORDER_WIDTH);
|
|
30623
|
+
return;
|
|
30624
|
+
}
|
|
30625
|
+
const existingKeys = new Set(
|
|
30626
|
+
this.triples.map((t) => `${t.subject}|${t.predicate}|${t.object.value}`)
|
|
30627
|
+
);
|
|
30628
|
+
const uniqueNew = newTriples.filter(
|
|
30629
|
+
(t) => !existingKeys.has(`${t.subject}|${t.predicate}|${t.object.value}`)
|
|
30630
|
+
);
|
|
30631
|
+
if (uniqueNew.length > 0) {
|
|
30632
|
+
this.triples = [...this.triples, ...uniqueNew];
|
|
30633
|
+
this.mergeNewTriples();
|
|
30634
|
+
}
|
|
30635
|
+
restoreNode(EXPANDED_BORDER_WIDTH);
|
|
30636
|
+
} catch (error) {
|
|
30637
|
+
if ((error == null ? void 0 : error.name) === "AbortError") {
|
|
30638
|
+
restoreNode(originalBorderWidth != null ? originalBorderWidth : DEFAULT_BORDER_WIDTH);
|
|
30639
|
+
return;
|
|
30640
|
+
}
|
|
30641
|
+
console.error("yasgui-graph-plugin: error expanding node", uri, error);
|
|
30642
|
+
restoreNode(originalBorderWidth != null ? originalBorderWidth : DEFAULT_BORDER_WIDTH);
|
|
30643
|
+
}
|
|
30644
|
+
}
|
|
30645
|
+
/**
|
|
30646
|
+
* Incrementally add new triples to the vis-network DataSets without a full redraw.
|
|
30647
|
+
* New nodes and edges are added; existing ones are left untouched.
|
|
30648
|
+
* Expects `this.triples` to already include the new triples.
|
|
30649
|
+
*/
|
|
30650
|
+
mergeNewTriples() {
|
|
30651
|
+
if (!this.triples || !this.prefixMap || !this.nodesDataSet || !this.edgesDataSet) return;
|
|
30652
|
+
const themeColors = getThemeNodeColors(this.currentTheme);
|
|
30653
|
+
const { nodes, edges } = triplesToGraph(this.triples, this.prefixMap, themeColors, this.settings);
|
|
30654
|
+
const existingValues = new Set(this.nodesDataSet.get().map((n) => n.fullValue));
|
|
30655
|
+
const nodesToAdd = nodes.filter((n) => !existingValues.has(n.fullValue));
|
|
30656
|
+
const existingEdgeKeys = new Set(
|
|
30657
|
+
this.edgesDataSet.get().map((e) => `${e.from}|${e.predicate}|${e.to}`)
|
|
30658
|
+
);
|
|
30659
|
+
const edgesToAdd = edges.filter(
|
|
30660
|
+
(e) => !existingEdgeKeys.has(`${e.from}|${e.predicate}|${e.to}`)
|
|
30661
|
+
);
|
|
30662
|
+
if (nodesToAdd.length > 0) {
|
|
30663
|
+
this.nodesDataSet.add(nodesToAdd);
|
|
30664
|
+
nodesToAdd.forEach((n) => {
|
|
30665
|
+
if (n.uri != null) {
|
|
30666
|
+
this.uriToNodeId.set(n.uri, n.id);
|
|
30667
|
+
}
|
|
30668
|
+
});
|
|
30669
|
+
}
|
|
30670
|
+
if (edgesToAdd.length > 0) {
|
|
30671
|
+
this.edgesDataSet.add(edgesToAdd);
|
|
30672
|
+
}
|
|
30673
|
+
}
|
|
30674
|
+
/**
|
|
29891
30675
|
* @returns Icon element
|
|
29892
30676
|
*/
|
|
29893
30677
|
getIcon() {
|
|
@@ -29908,6 +30692,11 @@ var GraphPlugin = class {
|
|
|
29908
30692
|
* Cleanup when plugin is destroyed
|
|
29909
30693
|
*/
|
|
29910
30694
|
destroy() {
|
|
30695
|
+
this.removeClickOutsideHandler();
|
|
30696
|
+
if (this.expansionAbortController) {
|
|
30697
|
+
this.expansionAbortController.abort();
|
|
30698
|
+
this.expansionAbortController = null;
|
|
30699
|
+
}
|
|
29911
30700
|
if (this.themeObserver) {
|
|
29912
30701
|
this.themeObserver.disconnect();
|
|
29913
30702
|
this.themeObserver = null;
|