@@ -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