Mercurial > code > home > repos > streamed-graph
changeset 8:6fefd287aff9
closer- element now holds a changing graph, but can't draw it yet
author | drewp@bigasterisk.com |
---|---|
date | Thu, 05 Dec 2019 01:32:13 -0800 |
parents | daf08d794660 |
children | 26d3e4860adc |
files | index.html src/graph_view.js src/graph_view.ts src/json_ld_quads.ts src/streamed-graph.ts src/streamed_graph_client.ts webpack-dev.config.ts |
diffstat | 7 files changed, 262 insertions(+), 278 deletions(-) [+] |
line wrap: on
line diff
--- a/index.html Wed Dec 04 23:44:29 2019 -0800 +++ b/index.html Thu Dec 05 01:32:13 2019 -0800 @@ -4,6 +4,6 @@ <h1>streamed-graph demo</h1> <script src="./build/streamed-graph.bundle.js"></script> - <streamed-graph url="something"></streamed-graph> + <streamed-graph url="http://bang5:9075/graph/events"></streamed-graph> </body> </html> \ No newline at end of file
--- a/src/graph_view.js Wed Dec 04 23:44:29 2019 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,202 +0,0 @@ -// from /my/site/homepage/www/rdf/browse/graphView.js - - -import { SuffixLabels } from './suffixLabels.js'; - -const groupByRdfType = (graph) => { - const env = graph.store.rdf; - const rdfType = env.createNamedNode('rdf:type'); - const byType = new Map(); // type : subjs - const untyped = new Set(); // subjs - graph.quadStore.quads({}, (q) => { - let subjType = null; - graph.quadStore.quads({subject: q.subject, - predicate: rdfType}, - (q2) => { subjType = q2.object; }); - if (subjType){ - subjType = subjType.toString(); - if (!byType.has(subjType)) { - byType.set(subjType, new Set()); - } - byType.get(subjType).add(q.subject.toString()); - } else { - untyped.add(q.subject.toString()); - } - - }); - return {byType: byType, untyped: untyped}; -}; - -const graphView = (graph) => { - const env = graph.store.rdf; - - const labels = new SuffixLabels(); - graph.quadStore.quads({}, (q) => { - if (q.subject.interfaceName == "NamedNode") { labels.planDisplayForNode(q.subject); } - if (q.predicate.interfaceName == "NamedNode") { labels.planDisplayForNode(q.predicate); } - if (q.object.interfaceName == "NamedNode") { labels.planDisplayForNode(q.object); } - if (q.object.interfaceName == "Literal" && q.object.datatype) { labels.planDisplayForNode(env.createNamedNode(q.object.datatype)); } - }); - - const rdfNode = (n) => { - if (n.interfaceName == "Literal") { - let dtPart = ""; - if (n.datatype) { - dtPart = html` - ^^<span class="literalType"> - ${rdfNode(env.createNamedNode(n.datatype))} - </span>`; - } - return html`<span class="literal">${n.nominalValue}${dtPart}</span>`; - } - if (n.interfaceName == "NamedNode") { - let dn = labels.getLabelForNode(n); - if (dn.match(/XMLSchema#.*/)) { dn = dn.replace('XMLSchema#', 'xsd:'); } - if (dn.match(/rdf-schema#.*/)) { dn = dn.replace('rdf-schema#', 'rdfs:'); } - return html`<a class="graphUri" href="${n.toString()}">${dn}</a>`; - } - - return html`[${n.interfaceName} ${n.toNT()}]`; - } - - const objBlock = (obj) => { - return html` - <div class="object"> - ${rdfNode(obj)} <!-- indicate what source or graph said this stmt --> - </div> - `; - }; - - /// bunch of table rows - const predBlock = (subj, pred) => { - const objsSet = new Set(); - graph.quadStore.quads({ subject: subj, predicate: pred }, (q) => { - - if (q.object.length) { - console.log(q.object) - } - objsSet.add(q.object); - }); - const objs = Array.from(objsSet.values()); - objs.sort(); - return html` - <div class="predicate">${rdfNode(pred)} - <div> - ${objs.map(objBlock)} - </div> - </div> - `; - }; - - const {byType, untyped} = groupByRdfType(graph); - const typedSubjs = Array.from(byType.keys()); - typedSubjs.sort(); - - const untypedSubjs = Array.from(untyped.values()); - untypedSubjs.sort(); - - const subjBlock = (subj) => { - const subjNode = env.createNamedNode(subj); - const predsSet = new Set(); - graph.quadStore.quads({ subject: subjNode }, (q) => { - predsSet.add(q.predicate); - }); - const preds = Array.from(predsSet.values()); - preds.sort(); - return html` - <div class="subject">${rdfNode(subjNode)} - <!-- todo: special section for uri/type-and-icon/label/comment --> - <div> - ${preds.map((p) => { return predBlock(subjNode, p); })} - </div> - </div> - `; - }; - const byTypeBlock = (typeUri) => { - const subjs = Array.from(byType.get(typeUri)); - subjs.sort(); - - const graphCells = new Map(); // [subj, pred] : objs - const preds = new Set(); - - subjs.forEach((subj) => { - graph.quadStore.quads({subject: env.createNamedNode(subj)}, (q) => { - preds.add(q.predicate.toString()); - const cellKey = subj + '|||' + q.predicate.toString(); - if (!graphCells.has(cellKey)) { - graphCells.set(cellKey, new Set()); - } - graphCells.get(cellKey).add(q.object); - }); - }); - const predsList = Array.from(preds); - predsList.splice(predsList.indexOf('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), 1); - // also pull out label, which should be used on 1st column - predsList.sort(); - - const thead = () => { - const predColumnHead = (pred) => { - return html`<th>${rdfNode(env.createNamedNode(pred))}</th>`; - }; - return html` - <thead> - <tr> - <th></th> - ${predsList.map(predColumnHead)} - </tr> - </thead>`; - }; - - const instanceRow = (subj) => { - const cell = (pred) => { - const objs = graphCells.get(subj + '|||' + pred); - if (!objs) { - return html`<td></td>`; - } - const objsList = Array.from(objs); - objsList.sort(); - const draw = (obj) => { - return html`<div>${rdfNode(obj)}</div>` - }; - return html`<td>${objsList.map(draw)}</td>`; - }; - - return html` - <tr> - <td>${rdfNode(env.createNamedNode(subj))}</td> - ${predsList.map(cell)} - </tr> - `; - }; - - return html` - <div>[icon] ${rdfNode(env.createNamedNode(typeUri))} resources</div> -<div class="typeBlockScroll"> - <table class="typeBlock"> - ${thead()} - ${subjs.map(instanceRow)} - </table> -</div> - `; - }; - - return html` - <link rel="stylesheet" href="/rdf/browse/style.css"> - - <section> - <h2> - Current graph (<a href="${graph.events.url}">${graph.events.url}</a>) - </h2> - <div> - <!-- todo: graphs and provenance. - These statements are all in the - <span data-bind="html: $root.createCurie(graphUri())">...</span> graph.--> - </div> - ${typedSubjs.map(byTypeBlock)} - <div class="spoGrid"> - ${untypedSubjs.map(subjBlock)} - </div> - </section> - `; -} -export { graphView }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/graph_view.ts Thu Dec 05 01:32:13 2019 -0800 @@ -0,0 +1,206 @@ +// from /my/site/homepage/www/rdf/browse/graphView.js + +import { html } from 'lit-html'; +import { SuffixLabels } from './suffixLabels.js'; +import { Store, Quad, DataFactory } from "n3" +const { namedNode, literal, quad } = DataFactory; + +const groupByRdfType = (graph: Store) => { + const env = graph.store.rdf; + const rdfType = env.createNamedNode('rdf:type'); + const byType = new Map(); // type : subjs + const untyped = new Set(); // subjs + graph.quadStore.quads({}, (q) => { + let subjType = null; + graph.quadStore.quads({ + subject: q.subject, + predicate: rdfType + }, + (q2) => { subjType = q2.object; }); + if (subjType) { + subjType = subjType.toString(); + if (!byType.has(subjType)) { + byType.set(subjType, new Set()); + } + byType.get(subjType).add(q.subject.toString()); + } else { + untyped.add(q.subject.toString()); + } + + }); + return { byType: byType, untyped: untyped }; +}; + +const graphView = (graph: Store) => { + const env = graph.store.rdf; + + const labels = new SuffixLabels(); + graph.quadStore.quads({}, (q) => { + if (q.subject.interfaceName == "NamedNode") { labels.planDisplayForNode(q.subject); } + if (q.predicate.interfaceName == "NamedNode") { labels.planDisplayForNode(q.predicate); } + if (q.object.interfaceName == "NamedNode") { labels.planDisplayForNode(q.object); } + if (q.object.interfaceName == "Literal" && q.object.datatype) { labels.planDisplayForNode(env.createNamedNode(q.object.datatype)); } + }); + + const rdfNode = (n) => { + if (n.interfaceName == "Literal") { + let dtPart: any = ""; + if (n.datatype) { + dtPart = html` + ^^<span class="literalType"> + ${rdfNode(env.createNamedNode(n.datatype))} + </span>`; + } + return html`<span class="literal">${n.nominalValue}${dtPart}</span>`; + } + if (n.interfaceName == "NamedNode") { + let dn = labels.getLabelForNode(n); + if (dn.match(/XMLSchema#.*/)) { dn = dn.replace('XMLSchema#', 'xsd:'); } + if (dn.match(/rdf-schema#.*/)) { dn = dn.replace('rdf-schema#', 'rdfs:'); } + return html`<a class="graphUri" href="${n.toString()}">${dn}</a>`; + } + + return html`[${n.interfaceName} ${n.toNT()}]`; + } + + const objBlock = (obj) => { + return html` + <div class="object"> + ${rdfNode(obj)} <!-- indicate what source or graph said this stmt --> + </div> + `; + }; + + /// bunch of table rows + const predBlock = (subj, pred) => { + const objsSet = new Set(); + graph.quadStore.quads({ subject: subj, predicate: pred }, (q) => { + + if (q.object.length) { + console.log(q.object) + } + objsSet.add(q.object); + }); + const objs = Array.from(objsSet.values()); + objs.sort(); + return html` + <div class="predicate">${rdfNode(pred)} + <div> + ${objs.map(objBlock)} + </div> + </div> + `; + }; + + const { byType, untyped } = groupByRdfType(graph); + const typedSubjs = Array.from(byType.keys()); + typedSubjs.sort(); + + const untypedSubjs = Array.from(untyped.values()); + untypedSubjs.sort(); + + const subjBlock = (subj) => { + const subjNode = env.createNamedNode(subj); + const predsSet = new Set(); + graph.quadStore.quads({ subject: subjNode }, (q) => { + predsSet.add(q.predicate); + }); + const preds = Array.from(predsSet.values()); + preds.sort(); + return html` + <div class="subject">${rdfNode(subjNode)} + <!-- todo: special section for uri/type-and-icon/label/comment --> + <div> + ${preds.map((p) => { return predBlock(subjNode, p); })} + </div> + </div> + `; + }; + const byTypeBlock = (typeUri) => { + const subjs = Array.from(byType.get(typeUri)); + subjs.sort(); + + const graphCells = new Map(); // [subj, pred] : objs + const preds = new Set(); + + subjs.forEach((subj) => { + graph.quadStore.quads({ subject: env.createNamedNode(subj) }, (q) => { + preds.add(q.predicate.toString()); + const cellKey = subj + '|||' + q.predicate.toString(); + if (!graphCells.has(cellKey)) { + graphCells.set(cellKey, new Set()); + } + graphCells.get(cellKey).add(q.object); + }); + }); + const predsList = Array.from(preds); + predsList.splice(predsList.indexOf('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), 1); + // also pull out label, which should be used on 1st column + predsList.sort(); + + const thead = () => { + const predColumnHead = (pred) => { + return html`<th>${rdfNode(env.createNamedNode(pred))}</th>`; + }; + return html` + <thead> + <tr> + <th></th> + ${predsList.map(predColumnHead)} + </tr> + </thead>`; + }; + + const instanceRow = (subj) => { + const cell = (pred) => { + const objs = graphCells.get(subj + '|||' + pred); + if (!objs) { + return html`<td></td>`; + } + const objsList = Array.from(objs); + objsList.sort(); + const draw = (obj) => { + return html`<div>${rdfNode(obj)}</div>` + }; + return html`<td>${objsList.map(draw)}</td>`; + }; + + return html` + <tr> + <td>${rdfNode(env.createNamedNode(subj))}</td> + ${predsList.map(cell)} + </tr> + `; + }; + + return html` + <div>[icon] ${rdfNode(env.createNamedNode(typeUri))} resources</div> +<div class="typeBlockScroll"> + <table class="typeBlock"> + ${thead()} + ${subjs.map(instanceRow)} + </table> +</div> + `; + }; + + return html` + <link rel="stylesheet" href="/rdf/browse/style.css"> + + <section> + <h2> + Current graph (<a href="${graph.events.url}">${graph.events.url}</a>) + </h2> + <div> + <!-- todo: graphs and provenance. + These statements are all in the + <span data-bind="html: $root.createCurie(graphUri())">...</span> graph.--> + </div> + ${typedSubjs.map(byTypeBlock)} + <div class="spoGrid"> + ${untypedSubjs.map(subjBlock)} + </div> + </section> + `; +} +export { graphView }
--- a/src/json_ld_quads.ts Wed Dec 04 23:44:29 2019 -0800 +++ b/src/json_ld_quads.ts Thu Dec 05 01:32:13 2019 -0800 @@ -1,46 +1,34 @@ -import { Quad, Node } from "./rdf_types"; import * as jsonld from "jsonld"; +import { DataFactory, Quad } from 'n3'; +const { namedNode, literal, quad } = DataFactory; +import ns from 'n3/src/IRIs'; +const {rdf} = ns; -function quadFromExpandedStatement(rdfEnv: any, subj: any, pred: string, obj: any, graphNode: any): Quad { - return { - subject: rdfEnv.createNamedNode(subj['@id']), - predicate: rdfEnv.createNamedNode(pred), - object: (obj['@id'] ? rdfEnv.createNamedNode(obj['@id']) : - rdfEnv.createLiteral(obj['@value'], obj['@language'], obj['@type'])), - graph: graphNode, - }; -} -function quadFromTypeStatement(rdfEnv: any, subj: any, obj: any, graphNode: any): Quad { - return { - subject: rdfEnv.createNamedNode(subj['@id']), - predicate: rdfEnv.createNamedNode('rdf:type'), - object: rdfEnv.createNamedNode(obj), - graph: graphNode, - }; -} - -export function eachJsonLdQuad(rdfEnv: any, jsonLdObj: object, onQuad: (Quad) => void, done: () => void) { +export function eachJsonLdQuad(jsonLdObj: object, onQuad: (q: Quad) => void, done: () => void) { jsonld.expand(jsonLdObj, function onExpand(err, expanded) { if (err) { throw new Error(); } (expanded as [object]).forEach(function (g) { var graph = g['@id']; - var graphNode = rdfEnv.createNamedNode(graph) as Node; + var graphNode = namedNode(graph); g['@graph'].forEach(function (subj) { + const subjNode = namedNode(subj['@id']); for (let pred in subj) { - if (pred.match(/^[^@]/)) { - subj[pred].forEach(function (obj) { - onQuad(quadFromExpandedStatement(rdfEnv, subj, pred, obj, graphNode)); - }); + if (pred === '@id') { + continue; } - else { - if (pred === "@type") { - subj[pred].forEach((obj) => { - onQuad(quadFromTypeStatement(rdfEnv, subj, obj, graphNode)); - }); - } + let predNode; + if (pred === "@type") { + predNode = namedNode(rdf.type); + } else { + predNode = namedNode(pred['@id']); } + subj[pred].forEach(function (obj) { + const objNode = (obj['@id'] ? namedNode(obj['@id']) : + literal(obj['@value'], obj['@language'] || obj['@type'])); + onQuad(quad(subjNode, predNode, objNode, graphNode)); + }); } }); });
--- a/src/streamed-graph.ts Wed Dec 04 23:44:29 2019 -0800 +++ b/src/streamed-graph.ts Thu Dec 05 01:32:13 2019 -0800 @@ -6,21 +6,18 @@ import { PolymerElement, html } from '@polymer/polymer'; import { customElement, property, computed } from '@polymer/decorators'; import { render } from 'lit-html'; -// import { graphView } from '/rdf/browse/graphView.js'; - - +import { graphView } from './graph_view'; +import { Store, DataFactory } from "n3" import { StreamedGraphClient } from './streamed_graph_client'; -console.log(StreamedGraphClient); - @customElement('streamed-graph') class StreamedGraph extends PolymerElement { @property({ type: String }) url: string = ''; @property({ type: Object }) - graph: Object; + graph: {version: number, graph: Store}; @property({ type: Boolean }) expanded: Boolean = false; @@ -45,13 +42,15 @@ StreamedGraph <a href="{{url}}">[source]</a>: {{status}} </div> - <div id="graphView">graph here - </div>`; + <div id="graphView"></div>`; } ready() { super.ready(); + this.graph = {version: -1, graph: null}; this.graphView = this.shadowRoot.getElementById("graphView"); + + this._onUrl(this.url); // todo: watch for changes and rebuild } toggleExpand(ev) { @@ -70,17 +69,18 @@ } _onUrl(url) { - // if (this.sg) { this.sg.close(); } - // this.sg = new StreamedGraphClient( - // url, - // this.onGraphChanged.bind(this), - // this.set.bind(this, 'status'), - // [],//window.NS, - // []); + if (this.sg) { this.sg.close(); } + this.sg = new StreamedGraphClient( + url, + this.onGraphChanged.bind(this), + this.set.bind(this, 'status'), + [],//window.NS, + [] + ); } onGraphChanged() { - //this.graph = { version: this.graph.version + 1, graph: this.sg }; + this.graph = { version: this.graph.version + 1, graph: this.sg.store }; if (this.expanded) { this.redrawGraph(); } @@ -88,7 +88,7 @@ _redrawLater() { if (!this.graphViewDirty) return; - //render(graphView(this.graph.graph), this.graphView); + render(graphView(this.graph.graph), this.graphView); this.graphViewDirty = false; }
--- a/src/streamed_graph_client.ts Wed Dec 04 23:44:29 2019 -0800 +++ b/src/streamed_graph_client.ts Thu Dec 05 01:32:13 2019 -0800 @@ -1,15 +1,18 @@ // from /my/site/homepage/www/rdf/streamed-graph.js import * as async from "async"; -import * as jsonld from "jsonld"; +// import * as jsonld from "jsonld"; -//import eachJsonLdQuad from "./json_ld_quads"; +import { eachJsonLdQuad } from "./json_ld_quads"; import { Store, DataFactory } from "n3" -/// <reference types="eventsource" /> -const EventSource = window.EventSource; +// /// <reference types="eventsource" /> +// const EventSource = window.EventSource; export class StreamedGraphClient { + // holds a n3 Store, which is synced to a server-side + // store that sends patches over SSE + onStatus: (msg: string) => void; onGraphChanged: () => void; store: Store; @@ -21,8 +24,6 @@ prefixes: Array<Record<string, string>>, staticGraphUrls: Array<string>) { console.log('new StreamedGraph', eventsUrl); - // holds a n3 Store, which is synced to a server-side - // store that sends patches over SSE this.onStatus = onStatus; this.onGraphChanged = onGraphChanged; this.onStatus('startup...'); @@ -60,7 +61,6 @@ throw new Error("zombie eventsource"); } - this.events = new EventSource(eventsUrl); this.events.addEventListener('error', (ev) => { @@ -77,7 +77,7 @@ this.events.addEventListener('fullGraph', (ev) => { this.onStatus('sync- full graph update'); let onReplaced = () => { - this.onStatus('synced'); + this.onStatus(`synced ${this.store.size}`); this.onGraphChanged(); }; this.replaceFullGraph(ev.data, onReplaced); @@ -86,7 +86,7 @@ this.events.addEventListener('patch', (ev) => { this.onStatus('sync- updating'); let onPatched = () => { - this.onStatus('synced'); + this.onStatus(`synced ${this.store.size}`); this.onGraphChanged(); }; this.patchGraph(ev.data, onPatched); @@ -95,25 +95,23 @@ } replaceFullGraph(jsonLdText: string, done: () => void) { - // this.quadStore.clear(); - // eachJsonLdQuad(this.store.rdf, JSON.parse(jsonLdText), - // this.quadStore.add.bind(this.quadStore), function () { - // done(); - // }); - // or this.store.insert([quad], quad.graph, function() {}); + this.store = new Store({}); + eachJsonLdQuad(JSON.parse(jsonLdText), + this.store.addQuad.bind(this.store), + done); } patchGraph(patchJson: string, done: () => void) { var patch = JSON.parse(patchJson).patch; async.series([ - // (done) => { - // eachJsonLdQuad(this.store.rdf, patch.deletes, - // this.quadStore.remove.bind(this.quadStore), done); - // }, (done) => { - // eachJsonLdQuad(this.store.rdf, patch.adds, - // this.quadStore.add.bind(this.quadStore), done); + eachJsonLdQuad(patch.deletes, + this.store.removeQuad.bind(this.store), done); + }, + (done) => { + eachJsonLdQuad(patch.adds, + this.store.addQuad.bind(this.store), done); }, /* seriesDone */ (done) => { done();
--- a/webpack-dev.config.ts Wed Dec 04 23:44:29 2019 -0800 +++ b/webpack-dev.config.ts Thu Dec 05 01:32:13 2019 -0800 @@ -29,12 +29,6 @@ liveReload: true, // doesn't work overlay: true, watchContentBase: true, - // proxy: { - // '/rdf': { - // target: 'https://bigasterisk.com/', - // //pathRewrite: {'^/api' : ''} - // } - // }, } };