Skip to content

Commit 32e3534

Browse files
committed
feat(explore graph): expand blank nodes by default
previously blank nodes would just show as literals now they are expanded upon discovery. However, it's limited to two layers of nested blank nodes.
1 parent 6449291 commit 32e3534

File tree

1 file changed

+60
-28
lines changed

1 file changed

+60
-28
lines changed

app.js

Lines changed: 60 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -680,7 +680,33 @@ function renderGraphVisualization(isExploreMode = false) {
680680
flashMessage(`Loading data for ${clickedNodeData.label}...`, false);
681681
document.querySelector('#queryLoadingIndicator').style.display = 'block';
682682

683-
const sparqlQuery = `SELECT ?p ?o WHERE { <${clickedNodeData.uri}> ?p ?o . } LIMIT 25`;
683+
const subject = clickedNodeData.uri;
684+
// we automatically expand two levels of blank nodes
685+
// as those can't be explored in the same way as URIs
686+
const sparqlQuery = `
687+
SELECT ?p ?o ?p2 ?o2 ?p3 ?o3
688+
WHERE {
689+
{
690+
<${subject}> ?p ?o .
691+
FILTER(!ISBLANK(?o))
692+
}
693+
UNION
694+
{
695+
<${subject}> ?p ?o .
696+
FILTER(ISBLANK(?o))
697+
?o ?p2 ?o2 .
698+
FILTER(!ISBLANK(?o2))
699+
}
700+
UNION
701+
{
702+
<${subject}> ?p ?o .
703+
FILTER(ISBLANK(?o))
704+
?o ?p2 ?o2 .
705+
FILTER(ISBLANK(?o2))
706+
?o2 ?p3 ?o3 .
707+
}
708+
}
709+
LIMIT 25`;
684710
const endpoint = yasqe.options.sparql.endpoint;
685711

686712
try {
@@ -707,49 +733,55 @@ function renderGraphVisualization(isExploreMode = false) {
707733
let newNodesAdded = false;
708734
let newLinksAdded = false;
709735

710-
sparqlResult.results.bindings.forEach(binding => {
711-
const predicateUri = binding.p.value;
736+
function processBinding(binding, parentNodeId, predicateUri) {
712737
const objectBinding = binding.o;
713738
const objectValue = objectBinding.value;
714-
const objectType = objectBinding.type; // 'uri', 'literal', 'bnode'
715-
716-
let objectLabel = objectValue.split('/').pop().split('#').pop();
717-
if (objectType === 'literal') {
718-
objectLabel = objectValue; // Use full literal value as label
719-
}
739+
const objectType = objectBinding.type;
720740

741+
let objectLabel = (objectType === 'literal') ? objectValue : objectValue.split('/').pop().split('#').pop();
742+
// blank nodes must be given unique IDs so that they don't connect to each other
743+
let targetNodeId = (objectType === 'bnode') ? `bnode::${parentNodeId}::${objectValue}` : objectValue;
721744

722-
let targetNodeId = objectValue;
723-
// For literals, we might want a more unique ID if multiple nodes can have same literal string
724745
if (objectType === 'literal') {
725-
targetNodeId = `literal::${clickedNodeData.id}::${predicateUri}::${objectValue}`;
746+
targetNodeId = `literal::${parentNodeId}::${predicateUri}::${objectValue}`;
726747
}
727748

728-
729749
if (!nodeMap.has(targetNodeId)) {
730-
const newNode = {
750+
nodes.push({
731751
id: targetNodeId,
732752
label: objectLabel,
733-
uri: objectType === 'uri' ? objectValue : null, // URI only if it's a URI
734-
isLiteral: objectType === 'literal',
735-
isExplored: false, // New nodes are not explored by default
736-
};
737-
nodes.push(newNode);
738-
nodeMap.set(targetNodeId, newNode);
753+
uri: (objectType === 'uri') ? objectValue : null,
754+
isExplored: (objectType === 'bnode'),
755+
});
756+
nodeMap.set(targetNodeId, nodes[nodes.length - 1]);
739757
newNodesAdded = true;
740758
}
741759

742760
const predicateLabel = predicateUri.split('/').pop().split('#').pop();
743-
const linkExists = links.some(l => l.source.id === clickedNodeData.id && l.target.id === targetNodeId && l.label === predicateLabel);
744-
745-
if (!linkExists) {
746-
links.push({
747-
source: clickedNodeData.id,
748-
target: targetNodeId,
749-
label: predicateLabel
750-
});
761+
if (!links.some(l => l.source.id === parentNodeId && l.target.id === targetNodeId)) {
762+
links.push({ source: parentNodeId, target: targetNodeId, label: predicateLabel });
751763
newLinksAdded = true;
752764
}
765+
766+
return targetNodeId;
767+
}
768+
769+
sparqlResult.results.bindings.forEach(binding => {
770+
const parentNodeId = clickedNodeData.id;
771+
const predicateUri = binding.p.value;
772+
const targetNodeId = processBinding(binding, parentNodeId, predicateUri);
773+
774+
if (binding.p2 && binding.o2) {
775+
const subBinding = { o: binding.o2 };
776+
const subPredicateUri = binding.p2.value;
777+
const subTargetNodeId = processBinding(subBinding, targetNodeId, subPredicateUri);
778+
779+
if (binding.p3 && binding.o3) {
780+
const nestedBinding = { o: binding.o3 };
781+
const nestedPredicateUri = binding.p3.value;
782+
processBinding(nestedBinding, subTargetNodeId, nestedPredicateUri);
783+
}
784+
}
753785
});
754786

755787
if (newNodesAdded || newLinksAdded) {

0 commit comments

Comments
 (0)