Coverage report: /development/source/library/org/datagraph/spocq-shard/src/core/encoding/sparql-results-circos.lisp
| Kind | Covered | All | % |
| expression | 0 | 643 | 0.0 |
| branch | 0 | 78 | 0.0 |
Key
Not instrumented
Conditionalized out
Executed
Not executed
Both branches taken
One branch taken
Neither branch taken
1
;;; -*- Mode: lisp; Syntax: ansi-common-lisp; Base: 10; Package: org.datagraph.spocq.implementation; -*-
3
;;; (load #p"patches/20160512-graphviz/mime-types.lisp")
4
;;; (load #p"patches/20160512-graphviz/sparql-results-circo.lisp")
5
;;; (sb-ext:save-lisp-and-die "sbcl-spocq-jsonld+revisions+dot.core")
6
(in-package :org.datagraph.spocq.implementation)
8
;;; results+circos serializer
9
;;; http://mkweb.bcgsc.ca/tableviewer/
13
;;; (load (compile-file "/development/source/library/org/datagraph/spocq/src/core/parameters.lisp"))
14
;;; (load (compile-file "/development/source/library/org/datagraph/spocq/src/core/encoding/sparql-results-circo.lisp"))
18
(:documentation "Circos Encoding"
19
"In a Circos projection(http://circos.ca), RDF nodes are projected into a sequence, which is arranged in a circle,
20
and links are drawn between the nodes. Variations in the projection
21
- combine nodes to create circos' atomic component (the 'cytogenetic band')
22
- collect nodes into segments circos' first-level elements (the 'chromosome')
23
- array nodes on data tracks
24
- combine segments into clrcle sectors (the 'karyotype')
25
- link immediately among nodes
26
- mediate links through nodes in each solution. (to depict the graph)
27
- selectively enable tracks as link sources and sinks.
28
according to different criteria and add additional bands within the circle, each of which anchors links
29
for a selected subset of the nodes.
31
alternative visualization:
32
- a general overview : http://www.mkbergman.com/414/large-scale-rdf-graph-visualization-tools/
33
- Cytoscape : incompatible with java 1.5
34
- OpenDX : dx does not compile on 10.4
35
- http://www.opendx.org/
36
- http://www-lsp.ujf-grenoble.fr/recherche/a3t2/a3t2a2/bahram/OpenDX/index.html
37
- RDF-Gravity : http://semweb.salzburgresearch.at/apps/rdf-gravity/index.html
38
- loads public repositories, eg http://dydra.com/jhacker/foaf.rdf
39
- requires rdf+xml, but does not (appear to) specify accept types, which means that, for a construct
40
or describe query response to succeed, the default response mime type must be application/rdf+xml
41
- SemaSpace : http://residence.aec.at/didi/FLweb/ (requires 3dvia, which requires a restart)
42
- VisualBrowser : http://nlp.fi.muni.cz/projekty/visualbrowser/index.html (fails to display its own examples)
43
- Walrus : http://www.caida.org/tools/visualization/walrus/ : requires a special file format
45
- mizbee : http://www.cs.utah.edu/~miriah/mizbee : chromasome mapping in processing -> js
47
the analogy to gene sequence mapping would be to array on the chromasome ring sequences of 'related' statements.
49
- pattern match (identity after wildcard substitution)
50
- membership in a graph (that's wildcard on spo w/ just the graph left to be equal)
51
- vocabulary constituency
55
(defparameter *circos-node-limit* 512)
56
(defparameter *circos-terms* ()
57
"a list of the terms which serve as the ideogram basis")
58
(defparameter *circos-require-iri-p* nil
59
"when true (default) encode only relations between iri terms")
61
(defclass occurrence-collector ()
63
:initform (make-hash-table :test 'equalp)
64
:accessor occurrence-collector-relations)
66
:initform (make-hash-table :test 'equalp)
67
:accessor occurrence-collector-labels)
69
:initform (make-hash-table :test 'equalp) :initarg :terms
70
:accessor occurrence-collector-terms)
72
:initform #'write-to-string :initarg :label-generator
73
:accessor occurrence-collector-label-generator)
75
:initform #'(lambda (s p o) (and s p o)) :initarg :filter
76
:accessor occurrence-collector-filter)))
78
(defclass direct-occurrence-collector (occurrence-collector)
81
(defun make-direct-occurrence-collector (&rest args)
82
(apply #'make-instance 'direct-occurrence-collector args))
84
(defmethod initialize-instance :after ((instance occurrence-collector) &key)
85
#+(or) ;; creates too many insignificant ideograms
86
(loop with relations = (occurrence-collector-relations instance)
88
do (progn (occurrence-collector-label instance term)
89
(setf (gethash term relations) nil)))
90
(unless (occurrence-collector-terms instance)
91
(setf (occurrence-collector-terms instance) (make-hash-table :test 'equalp))))
93
(defgeneric collect-occurrence (collector from relation to)
94
(:method ((collector direct-occurrence-collector) from (relation t) to)
95
(when (funcall (occurrence-collector-filter collector) from relation to)
96
(let* ((relations (occurrence-collector-relations collector)))
97
(unless (gethash to relations)
98
;; ensure it is present as a key with no content, but do not overwrite anything present
99
(setf (gethash to relations) nil))
100
(let ((entry (gethash from relations)))
102
(let ((connection (assoc to entry)))
104
(incf (rest connection))
105
(setf (gethash from relations) (acons to 1 entry)))))
108
(setf (gethash from relations) (acons to 1 nil)))))))))
110
(defun occurrence-collector-values (collector)
111
(loop for value being each hash-key of (occurrence-collector-relations collector)
112
for i below *circos-node-limit*
115
(defun occurrence-collector-correlated-values (collector value)
116
(gethash value (occurrence-collector-relations collector)))
118
(defun escape-circos-label (label)
119
(loop with escape-count = 0
120
for i below (length label)
121
for char across label
122
;;; it is a rather incapable syntax
123
unless (find char "01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-.")
124
do (progn (unless (plusp escape-count)
125
(setf label (copy-seq label)))
126
(setf (char label i) #\.)))
129
(defgeneric occurrence-collector-label (collector node)
130
(:method ((collector occurrence-collector) node)
131
(or (gethash node (occurrence-collector-labels collector))
132
(let ((label (funcall (occurrence-collector-label-generator collector) node)))
133
(setf label (escape-circos-label label))
134
(when (gethash label (occurrence-collector-terms collector))
135
(setf label (string (gensym label))))
136
(setf (gethash label (occurrence-collector-terms collector)) node)
137
(setf (gethash node (occurrence-collector-labels collector))
141
;;; writing - either just bindings
143
(defun write-occurrence-table (collector stream)
144
(let ((values (remove-duplicates (occurrence-collector-values collector) :test #'equalp)))
145
(log-debug "write-occurrence-table: x~s values: ~s" (length values) values)
146
(write-string "labels" stream)
147
(loop for from-value in values
148
do (format stream "~a\"~a\"" #\tab (occurrence-collector-label collector from-value)))
149
(loop with cell-count = 0 with connection-count = 0
150
for from-value in values
151
do (progn (format stream "~%\"~a\"" (occurrence-collector-label collector from-value))
152
(loop with to-occurrences = (occurrence-collector-correlated-values collector from-value)
153
for to-value in values
154
for to-occurrence = (rest (assoc to-value to-occurrences :test #'eql))
155
do (progn (format stream "~a~a" #\tab (if (numberp to-occurrence) to-occurrence "-"))
156
(when (numberp to-occurrence) (incf cell-count) (incf connection-count to-occurrence)))))
157
finally (progn (log-debug "write-occurrence-table: cell count: ~s, connection count: ~s"
161
(return cell-count)))))
163
(defgeneric write-sparql-results+circos (results stream &key terms)
164
(:documentation "Encode the result field to the stream as a circos connection table.
165
Return the count of links actually encoded.")
167
(:method ((result symbol) (stream t) &key terms)
168
(declare (ignore terms))
172
(:method ((result list-solution-field) (stream t) &rest args)
173
(apply #'write-sparql-results+circos
174
(cons (list-solution-field-dimensions result)
175
(list-solution-field-solutions result))
178
(:method ((results cons) (stream t) &key (terms *circos-terms*))
179
(let* ((dimensions (first results))
180
(solutions (rest results))
183
(start (or (response-offset) 0))
185
(label-stream (make-string-output-stream))
186
(*expand-literal-values* nil)
187
(*namespace-bindings* (metadata-namespace-bindings *task*)))
188
(flet ((encode-term-label (term)
192
;; try to parse the iri to eliminate some components, but
193
;; fall back on the full namestring
194
(setf label (or (ignore-errors (iri-label term))
195
(spocq:iri-lexical-form term))))
197
(encode-turtle-object term label-stream)
198
(setf label (get-output-stream-string label-stream))))
199
(when (zerop (length label))
200
(log-warn "bad label: ~s" term)
201
(setf label (string (gensym "badlabel"))))
203
(let ((collector (make-direct-occurrence-collector
205
:label-generator #'encode-term-label
206
:filter #'(lambda (s p o)
208
(when (or (null *circos-require-iri-p*)
209
(and (iri-p s) (iri-p o)))
211
(flet ((put-graph-field ()
212
(log-debug "write-sparql-results+circos: graph field")
213
(loop for solution in solutions
214
for (s p o) = solution
215
until (and end (>= index end))
218
do (collect-occurrence collector s p o)))
219
(put-solution-field ()
220
(log-debug "write-sparql-results+circos: solution field")
221
(dolist (solution solutions)
222
(when (>= index start)
223
(when (and end (>= index end))
225
(loop for values on solution
226
for names on dimensions
227
for from-value = (first values)
229
do (loop for to-value in (rest values)
230
for to-name in (rest names)
232
do (collect-occurrence collector from-value to-name to-value)))
234
(log-warn "write-sparql-results+circos: dimensions ~s" dimensions)
235
(cond ((or (equal dimensions *construct-dimensions*)
236
(equal dimensions *quad-dimensions*))
239
(put-solution-field))
242
(log-warn "write-sparql-results+circos: solutions ~s / ~s" count index)
243
(write-occurrence-table collector stream)
244
(incf-stat *statements-returned* index)
247
(:method ((results boolean-generator) (stream t) &key terms)
248
(declare (ignore terms))
252
(:method ((results solution-generator) (stream t) &key (terms *circos-terms*))
253
(let* ((dimensions (solution-generator-dimensions results))
254
(channel (solution-generator-channel results))
255
(base-width (length dimensions))
258
(start (or (response-offset) 0))
260
(label-stream (make-string-output-stream))
261
(*expand-literal-values* nil)
262
(*namespace-bindings* (metadata-namespace-bindings *task*)))
263
(flet ((encode-term-id-label (term-id)
266
(-1 "urn:dydra:default")
267
(-2 "urn:dydra:named")
268
(t (encode-turtle-term-number term-id label-stream)
269
(let ((label (get-output-stream-string label-stream)))
270
(when (repository-term-is-iri *transaction* term-id)
271
(setf label (or (ignore-errors (iri-label label))
273
(when (zerop (length label))
274
(log-warn "bad label: ~s" term-id)
275
(setf label (string (gensym "label"))))
277
(let ((collector (make-direct-occurrence-collector
279
:label-generator #'encode-term-id-label
280
:filter #'(lambda (s p o)
282
(when (or (null *circos-require-iri-p*)
283
(and (repository-term-is-iri *transaction* s)
284
(repository-term-is-iri *transaction* o)))
286
(flet ((do-solution-pages ()
287
(do-pages (page channel)
288
(cond ((and (= base-width (array-dimension page 1))
289
(= (length (array-dimensions page)) 2))
290
(if (and end (>= index end))
292
(if (>= (+ index (array-dimension page 0)) start)
293
(dotimes (page-index (array-dimension page 0))
294
(when (>= index start)
295
(when (and end (>= index end))
297
(loop for from-index from 0 below base-width
298
for names on dimensions
299
for from-value = (aref page page-index from-index)
300
when (plusp from-value)
301
do (loop for to-index from (1+ from-index) below base-width
303
for to-value = (aref page page-index to-index)
304
when (plusp to-value)
305
;; no need to externalize, they are just counted
306
do (collect-occurrence collector from-value name to-value))))
308
; otherwise skip the entire page
309
(incf index (array-dimension page 0)))))
311
(log-warn "field width mismatch: ~s : ~s."
312
dimensions (array-dimension page 1))))
313
(incf index (array-dimension page 0))))
315
(do-pages (page channel)
316
(cond ((and (= base-width (array-dimension page 1))
317
(= (length (array-dimensions page)) 2))
318
(if (and end (>= index end))
320
(if (>= (+ index (array-dimension page 0)) start)
321
(dotimes (page-index (array-dimension page 0))
322
(when (>= index start)
323
(when (and end (>= index end))
325
(let* ((s-id (aref page page-index 0))
326
(p-id (aref page page-index 1))
327
(o-id (aref page page-index 2))) ;; ignore graph
329
(collect-occurrence collector s-id p-id o-id))))
331
; otherwise skip the entire page
332
(incf index (array-dimension page 0)))))
334
(log-warn "field width mismatch: ~s : ~s."
335
dimensions (array-dimension page 1))))
336
(incf index (array-dimension page 0)))))
337
(log-warn "write-sparql-results+circos: dimensions ~s" dimensions)
338
(cond ((or (equal dimensions *construct-dimensions*)
339
(equal dimensions *quad-dimensions*))
345
(log-warn "write-sparql-results+circos: solutions ~s / ~s" count index)
346
(write-occurrence-table collector stream)
347
(incf-stat *statements-returned* index)
353
(defmethod send-response-message (operation (message t) (stream t) (content-type mime:*/sparql-results+circos))
354
"Given a MESSAGE, and a STREAM with a generic circos CONTENT-TYPE, encode as a circos data table"
355
(when *encoding-trace-output*
356
(setf stream (make-broadcast-stream *encoding-trace-output* stream)))
357
(let ((*package* *spocq-reader-package*))
358
(write-sparql-results+circos message stream)))
360
(defmethod send-response-message ((operation t) (generator t) (stream t) (content-type mime:image/circos))
361
"Given a solution field, emit the results as a circos tabular document and render that as an image"
362
(let ((pathname (tmp-export-pathname (account (repository-account *repository*))
363
(repository *repository*))))
365
(when (plusp (with-open-file (tsv-stream pathname :direction :output
367
:if-does-not-exist :create)
368
(call-next-method operation generator tsv-stream content-type)))
369
;; require encoded content before rendering
370
(render-circos-file pathname stream content-type))
371
(conditional-delete-file pathname))))
373
(defun circos-executable-pathname ()
374
(concatenate 'string "/opt/rails/script/circos/circos-table"))
376
(defgeneric render-circos-file (from to media-type)
377
;; type eventually to allow png v/s svg
378
(:method ((from pathname) (to stream) (type string))
379
(let ((process (run-program "/bin/bash"
380
(list (circos-executable-pathname)
387
(case (run-program-exit-code process)
390
(write-string (read-file from) *trace-output*)
391
(let ((content (read-file from)))
392
(write-string content to))
393
(error "circos generation failed: ~s." (run-program-exit-code process))))
394
(run-program-close process))
396
(error "circos generation failed.")))))
397
(:method ((from t) (to pathname) (type string))
398
(with-open-file (stream to :direction :output)
399
(render-circos-file from stream type)))
401
(:method ((from t) (to t) (type mime:image/circos))
402
(render-circos-file from to (de.setf.utility.implementation::mime-type-file-type type))))
404
(defgeneric circos-sparql (query pathname &rest args)
405
(:method ((query t) (destination pathname) &rest args)
406
(with-open-file (output-stream destination :direction :output
407
:element-type 'character
408
:if-does-not-exist :create :if-exists :supersede)
409
(apply #'circos-sparql query output-stream args)))
410
(:method ((query t) (destination stream) &rest args)
411
(let* ((type (pathname-type destination))
412
(response-content-type (cond ((equalp type "txt") MIME:APPLICATION/SPARQL-RESULTS+CIRCOS)
413
((equalp type "svg") MIME:IMAGE/VND.DYDRA.SPARQL-RESULTS+CIRCOS+SVG+XML)
414
(t (error "anomalous file type: ~s" type)))))
415
(multiple-value-bind (results dimensions *task*)
416
(apply #'run-sparql query args)
417
(let ((*expand-literal-values* nil)
418
(*repository* (task-repository *task*)))
420
; (write-sparql-results+circos (cons dimensions results) destination)
421
(send-response-message :query (cons dimensions results) destination response-content-type)))))))
423
(write-sparql-results+circos '((?::a ?::b) (1 2) (1 1) (3 1)) *trace-output*)
425
(circos-sparql "select ?s ?p ?o where {?s ?p ?o}" #p"/tmp/test.txt" :repository-id "schema/owl")
426
(render-circos-file #p"/tmp/test.txt" #p"/tmp/test.svg" "svg")
429
(circos-sparql "SELECT * WHERE {
431
FILTER regex(str(?c), '[^.;>]\\\\s*$')
432
}" #p"/tmp/test.txt" :repository-id "danbri/schema-org")
434
(ORG.DATAGRAPH.SPOCQ.IMPLEMENTATION::WRITE-SPARQL-RESULTS+CIRCOS
436
("UserInteraction and its subtypes is an old way of talking about users interacting with pages. It is generally better to use
437
<a href=\"/Action\">Action</a>-based vocabulary, alongside types such as <a href=\"/Comment\">Comment</a>.
439
<http://schema.org/UserComments>))
442
(compute-instance-model (repository "saam-mirror/saam") mime:application/json)
444
(compute-instance-model (repository "saam-mirror/saam") mime:image/vnd.dydra.sparql-results+circos+svg+xml)
446
(repository-referenced-pattern-classes (repository "saam-mirror/saam"))
448
(repository-pattern-classes (repository "saam-mirror/saam"))
450
curl https://de17.dydra.com/system/accounts/saam-mirror/repositories/saam -H "Accept: image/vnd.dydra.sparql-results+circos+svg+xml"
451
curl https://de17.dydra.com/system/accounts/saam-mirror/repositories/saam -H "Accept: text/sparql-results+circos"
454
(write-sparql-results+circos (cons *construct-dimensions*
459
(send-response-message :query (cons *construct-dimensions*
460
'((<http://example.org/s> <http://example.org/p1> <http://example.org/o>)
461
(<http://example.org/s> <http://example.org/p2> <http://example.org/o2>)))
463
mime:*/sparql-results+circos)
465
(let ((*repository* (repository "test/test")))
466
(send-response-message :query (cons *construct-dimensions*
467
'((<http://example.org/s> <http://example.org/p1> <http://example.org/o>)
468
(<http://example.org/s> <http://example.org/p2> <http://example.org/o2>)))
470
mime:image/vnd.dydra.sparql-results+circos+svg+xml))
472
(with-open-file (output "/tmp/circos-select.txt" :direction :output :if-exists :supersede :if-does-not-exist :create)
473
(send-response-message :query (compute-instance-model (repository "saam-mirror/saam")
474
(mime-type "image/vnd.dydra.sparql-results+circos+svg+xml;profile=http://spinrdf.org/sp#Select"))
476
mime:*/sparql-results+circos))
478
(with-open-file (output "/tmp/circos-graph.txt" :direction :output :if-exists :supersede :if-does-not-exist :create)
479
(send-response-message :query (compute-instance-model (repository "saam-mirror/saam")
480
(mime-type "image/vnd.dydra.sparql-results+circos+svg+xml;profile=http://spinrdf.org/sp#Construct"))
482
mime:*/sparql-results+circos))
484
(let ((*repository* (repository "test/test")))
485
(send-response-message :query (cons *construct-dimensions*
486
(compute-instance-model (repository "saam-mirror/saam") mime:image/vnd.dydra.sparql-results+circos+svg+xml))
488
mime:image/vnd.dydra.sparql-results+circos+svg+xml))