import { Component , OnInit , OnChanges , SimpleChanges , ViewChild , ElementRef , Input , Output , EventEmitter , HostListener } from '@angular/core' ; import * as N3 from 'n3' ; import * as _ from 'lodash' ; import * as screenfull from 'screenfull' ; import * as d3_save_svg from 'd3-save-svg' ; import { PrefixSimplePipe } from '../pipes/prefix-simple.pipe' ; declare const d3 ; export interface Node { id : string ; label : string ; weight : number ; type : string ; owlClass ? : boolean ; instance ? : boolean ; } export interface Link { source : Node ; target : Node ; predicate : string ; weight : number ; } export interface Triples { s : Node ; p : Node ; o : Node ; } export interface Graph { nodes : Node [ ] ; links : Link [ ] ; triples : Triples [ ] ; } @ Component ( { selector : 'app-sparql-force' , templateUrl : './sparql-force.component.html' , styleUrls : [ './sparql-force.component.css' ] } ) export class SparqlForceComponent implements OnInit { private graph : Graph ; private svg ; private force ; public fs : boolean = false ; private divWidth ; private divHeight ; private widthBeforeResize ; private limit : string = "100" ; private timeEnter : number ; private timeOut : number ; @ ViewChild ( 'chart' ) private chartContainer : ElementRef ; @ Input ( ) public data : Array < any > ; @ Input ( ) public height : number ; @ Output ( ) clickedURI = new EventEmitter < string > ( ) ; constructor ( private prefixSimplePipe ) { } ngOnInit ( ) { if ( this . data ) { if ( this . height ) { this . divHeight = this . height ; } else { this . divHeight = this . getContainerHeight ( ) ? this . getContainerHeight ( ) : 500 ; } this . createChart ( ) ; } this . getContainerHeight ( ) ; } fullscreen ( ) { this . fs = this . fs ? false : true ; var el = this . chartContainer . nativeElement ; if ( this . fs ) this . widthBeforeResize = el . clientWidth ; if ( screenfull . enabled ) { screenfull . toggle ( el ) ; } } ngOnChanges ( changes ) { if ( changes . data . currentValue ) { this . data = changes . data . currentValue ; this . redraw ( ) ; } } getContainerHeight ( ) { return this . chartContainer . nativeElement . clientHeight ; } redraw ( ) { this . cleanGraph ( ) ; this . attachData ( ) ; } @ HostListener ( 'window:resize' ) onResize ( ) { var el = this . chartContainer . nativeElement ; if ( this . chartContainer ) { if ( ! this . fs && this . widthBeforeResize ) { this . divWidth = this . widthBeforeResize ; this . widthBeforeResize = null ; } else { this . divWidth = el . clientWidth ; } this . divHeight = this . fs ? el . clientHeight : this . height ; d3 . selectAll ( "svg" ) . remove ( ) ; this . createChart ( ) ; } } @ HostListener ( 'mousewheel' , [ '$event' ] ) onScroll ( ev ) { var delta = Math . max ( - 1 , Math . min ( 1 , ( ev . wheelDelta || - ev . detail ) ) ) ; if ( delta > 0 ) { console . log ( "zoom in" ) ; } else if ( delta < 0 ) { console . log ( "zoom out" ) ; } } saveSVG ( ) { var config = { filename : 'sparql-viz-graph' , } ; d3_save_svg . save ( d3 . select ( 'svg' ) . node ( ) , config ) ; } createChart ( ) { const element = this . chartContainer . nativeElement ; if ( ! this . divWidth ) this . divWidth = element . clientWidth ; if ( ! this . divHeight ) this . divHeight = element . clientHeight ; this . svg = d3 . select ( element ) . append ( 'svg' ) . attr ( 'width' , this . divWidth ) . attr ( 'height' , this . divHeight ) ; this . attachData ( ) ; } attachData ( ) { this . force = d3 . layout . force ( ) . size ( [ this . divWidth , this . divHeight ] ) ; var limit = parseInt ( this . limit ) ; if ( this . data . length > limit ) { var triples = this . data . slice ( 0 , limit ) ; } else { var triples = this . data ; } if ( typeof triples === 'string' ) { this . _parseTriples ( triples ) . then ( d => { console . log ( d ) ; var abr = this . _abbreviateTriples ( d ) ; this . graph = this . _triplesToGraph ( abr ) ; this . updateChart ( ) ; } ) ; } else { this . graph = this . _triplesToGraph ( triples ) ; this . updateChart ( ) ; } } public clicked ( d ) { if ( d3 . event . defaultPrevented ) return ; this . clickedURI . emit ( d ) ; } cleanGraph ( ) { d3 . selectAll ( "svg > *" ) . remove ( ) ; } updateChart ( ) { if ( ! this . svg ) return ; this . svg . append ( "svg:defs" ) . selectAll ( "marker" ) . data ( [ "end" ] ) . enter ( ) . append ( "svg:marker" ) . attr ( "id" , String ) . attr ( "viewBox" , "0 -5 10 10" ) . attr ( "refX" , 30 ) . attr ( "refY" , - 0.5 ) . attr ( "markerWidth" , 6 ) . attr ( "markerHeight" , 6 ) . attr ( "orient" , "auto" ) . append ( "svg:polyline" ) . attr ( "points" , "0,-5 10,0 0,5" ) ; var links = this . svg . selectAll ( ".link" ) . data ( this . graph . triples ) . enter ( ) . append ( "path" ) . attr ( "marker-end" , "url(#end)" ) . attr ( "class" , "link" ) ; var linkTexts = this . svg . selectAll ( ".link-text" ) . data ( this . graph . triples ) . enter ( ) . append ( "text" ) . attr ( "class" , "link-text" ) . text ( d => d . p . label ) ; var nodeTexts = this . svg . selectAll ( ".node-text" ) . data ( this . _filterNodesByType ( this . graph . nodes , "node" ) ) . enter ( ) . append ( "text" ) . attr ( "class" , "node-text" ) . text ( d => d . label ) ; var nodes = this . svg . selectAll ( ".node" ) . data ( this . _filterNodesByType ( this . graph . nodes , "node" ) ) . enter ( ) . append ( "circle" ) . attr ( "class" , d => { if ( d . owlClass ) { return "class" ; } else if ( d . label . indexOf ( "_:" ) != - 1 ) { return "blank" ; } else if ( d . instance || d . label . indexOf ( "inst:" ) != - 1 ) { return "instance" ; } else { return "node" ; } } ) . attr ( "id" , d => d . label ) . attr ( "r" , d => { if ( d . label . indexOf ( "_:" ) != - 1 ) { return 7 ; } else if ( d . instance || d . label . indexOf ( "inst:" ) != - 1 ) { return 10 ; } else if ( d . owlClass || d . label . indexOf ( "inst:" ) != - 1 ) { return 9 ; } else { return 8 ; } } ) . on ( "click" , ( d ) => { this . clicked ( d ) ; } ) . call ( this . force . drag ) ; this . force . on ( "tick" , ( ) => { nodes . attr ( "cx" , d => d . x ) . attr ( "cy" , d => d . y ) ; links . attr ( "d" , function ( d ) { return "M" + d . s . x + "," + d . s . y + "S" + d . p . x + "," + d . p . y + " " + d . o . x + "," + d . o . y ; } ) ; nodeTexts . attr ( "x" , d => d . x + 12 ) . attr ( "y" , d => d . y + 3 ) ; linkTexts . attr ( "x" , d => { return 4 + ( d . s . x + d . p . x + d . o . x ) / 3 ; } ) . attr ( "y" , d => { return 4 + ( d . s . y + d . p . y + d . o . y ) / 3 ; } ) ; } ) ; this . force . nodes ( this . graph . nodes ) . links ( this . graph . links ) . charge ( - 500 ) . linkDistance ( 50 ) . start ( ) ; } private _filterNodesById ( nodes , id ) { return nodes . filter ( n => n . id === id ) ; } private _filterNodesByType ( nodes , value ) { return nodes . filter ( n => n . type === value ) ; } private _triplesToGraph ( triples ) { if ( ! triples ) return ; var graph = { nodes : [ ] , links : [ ] , triples : [ ] } ; triples . forEach ( triple => { var subjId = this . prefixSimplePipe . transform ( triple . subject ) ; var predId = this . prefixSimplePipe . transform ( triple . predicate ) ; var objId = this . prefixSimplePipe . transform ( triple . object ) ; if ( ! isNaN ( objId ) ) { objId = Number ( objId ) % 1 == 0 ? String ( Number ( objId ) ) : String ( Number ( objId ) . toFixed ( 2 ) ) ; } var subjNode = this . _filterNodesById ( graph . nodes , subjId ) [ 0 ] ; var objNode = this . _filterNodesById ( graph . nodes , objId ) [ 0 ] ; var predNode = { id : predId , label : predId , weight : 1 , type : "pred" } ; graph . nodes . push ( predNode ) ; if ( subjNode == null ) { subjNode = { id : subjId , label : subjId , weight : 1 , type : "node" } ; graph . nodes . push ( subjNode ) ; } if ( objNode == null ) { objNode = { id : objId , label : objId , weight : 1 , type : "node" } ; if ( predNode . label == "rdf:type" || predNode . label == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" ) { objNode . owlClass = true ; subjNode . instance = true ; } graph . nodes . push ( objNode ) ; } var blankLabel = "" ; graph . links . push ( { source : subjNode , target : predNode , predicate : blankLabel , weight : 1 } ) ; graph . links . push ( { source : predNode , target : objNode , predicate : blankLabel , weight : 1 } ) ; graph . triples . push ( { s : subjNode , p : predNode , o : objNode } ) ; } ) ; return graph ; } private _parseTriples ( triples ) { var parser = N3 . Parser ( ) ; var jsonTriples = [ ] ; return new Promise ( ( resolve , reject ) => { parser . parse ( triples , ( err , triple , prefixes ) => { if ( triple ) { jsonTriples . push ( triple ) ; } else { resolve ( { triples : jsonTriples , prefixes : prefixes } ) ; } if ( err ) { reject ( err ) ; } } ) ; } ) ; } private _abbreviateTriples ( data ) { var prefixes = data . prefixes ; var triples = [ ] ; function abbreviate ( foi ) { var newVal = null ; if ( foi . indexOf ( 'http' ) !== - 1 ) { _ . each ( prefixes , ( val , key ) => { if ( foi . indexOf ( val ) !== - 1 ) { newVal = foi . replace ( val , key + ':' ) ; } } ) ; } return newVal ; } _ . each ( data . triples , d => { var s = d . subject ; var p = d . predicate ; var o = d . object ; if ( abbreviate ( s ) != null ) s = abbreviate ( s ) ; if ( abbreviate ( p ) != null ) p = abbreviate ( p ) ; if ( abbreviate ( o ) != null ) o = abbreviate ( o ) ; triples . push ( { subject : s , predicate : p , object : o } ) ; } ) ; console . log ( triples ) ; return triples ; } }