Mercurial > code > home > repos > streamed-graph
view src/graph_view.ts @ 45:3e414d575d96
can't use that graph property so well if it's non-notifying
author | drewp@bigasterisk.com |
---|---|
date | Fri, 03 Jan 2020 23:42:04 -0800 |
parents | 648bd89f9d47 |
children | c16a331f42e5 |
line wrap: on
line source
import { html, TemplateResult } from 'lit-html'; import { Quad, Term, N3Store } from 'n3'; import { DataFactory, Util } from 'n3'; const { namedNode } = DataFactory; import * as RDF from "rdf-js"; type NamedNode = RDF.NamedNode; import { SuffixLabels } from './suffixLabels'; // import ns from 'n3/src/IRIs'; // const { rdf } = ns; const rdf = { type: namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")}; type TypeToSubjs = Map<NamedNode, Set<NamedNode>>; function groupByRdfType(graph: N3Store): { byType: TypeToSubjs, untyped: Set<NamedNode> } { const rdfType = rdf.type; const byType: TypeToSubjs = new Map(); const untyped: Set<NamedNode> = new Set(); // subjs const internSubjs = new Map<string, NamedNode>(); graph.forEach((q) => { if (!Util.isNamedNode(q.subject)) { throw new Error("unsupported " + q.subject.value); } const subj = q.subject as NamedNode; let subjType: NamedNode | null = null; graph.forObjects((o: Quad) => { if (Util.isNamedNode(o.object)) { subjType = o.object as NamedNode; } }, subj, rdfType, null); if (subjType !== null) { // (subj, rdf:type, subjType) in graph if (!byType.has(subjType)) { byType.set(subjType, new Set()); } (byType.get(subjType) as Set<NamedNode>).add(subj); } else { // no rdf:type stmt in graph if (!internSubjs.has(subj.value)) { internSubjs.set(subj.value, subj); } const intSubj: NamedNode = internSubjs.get(subj.value as string) as NamedNode; untyped.add(intSubj); } }, null, null, null, null); return { byType: byType, untyped: untyped }; } class NodeDisplay { labels: SuffixLabels; constructor(labels: SuffixLabels) { this.labels = labels; } getHtml(n: Term|NamedNode): TemplateResult { if (n.termType == "Literal") { let dtPart: any = ""; if (n.datatype) { dtPart = html` ^^<span class="literalType"> ${this.getHtml(n.datatype)} </span>`; } return html`<span class="literal">${n.value}${dtPart}</span>`; } if (n.termType == "NamedNode") { let shortened = false; let uriValue: string = n.value; for (let [long, short] of [ ["http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:"], ["http://www.w3.org/2000/01/rdf-schema#", "rdfs:"], ["http://purl.org/dc/elements/1.1/", "dc:"], ["http://www.w3.org/2001/XMLSchema#", "xsd:"]]) { if (uriValue?.startsWith(long)) { uriValue = short + uriValue.substr(long.length); shortened = true; break; } } if (!shortened) { let dn: string | undefined = this.labels.getLabelForNode(uriValue); if (dn === undefined) { throw new Error(`dn=${dn}`); } uriValue = dn; } return html`<a class="graphUri" href="${n.value}">${uriValue}</a>`; } return html`[${n.termType} ${n.value}]`; } } export class GraphView { url: string; graph: N3Store; nodeDisplay: NodeDisplay; constructor(url: string, graph: N3Store) { this.url = url; this.graph = graph; const labels = new SuffixLabels(); this._addLabelsForAllTerms(labels); this.nodeDisplay = new NodeDisplay(labels); } _addLabelsForAllTerms(labels: SuffixLabels) { return this.graph.forEach((q: Quad) => { if (q.subject.termType === "NamedNode") { labels.planDisplayForNode(q.subject); } if (q.predicate.termType === "NamedNode") { labels.planDisplayForNode(q.predicate); } if (q.object.termType === "NamedNode") { labels.planDisplayForNode(q.object); } if (q.object.termType === "Literal" && q.object.datatype) { labels.planDisplayForNode(q.object.datatype); } }, null, null, null, null); } _subjBlock(subj: NamedNode) { const predsSet: Set<NamedNode> = new Set(); this.graph.forEach((q: Quad) => { predsSet.add(q.predicate as NamedNode); }, subj, null, null, null); const preds = Array.from(predsSet.values()); preds.sort(); return html` <div class="subject">${this.nodeDisplay.getHtml(subj)} <!-- todo: special section for uri/type-and-icon/label/comment --> <div> ${preds.map((p) => { return this._predBlock(subj, p); })} </div> </div> `; } _objBlock(obj: Term) { return html` <div class="object"> ${this.nodeDisplay.getHtml(obj)} <!-- indicate what source or graph said this stmt --> </div> `; } _predBlock(subj: NamedNode, pred: NamedNode) { const objsSet = new Set<Term>(); this.graph.forEach((q: Quad) => { objsSet.add(q.object); }, subj, pred, null, null); const objs = Array.from(objsSet.values()); objs.sort(); return html` <div class="predicate">${this.nodeDisplay.getHtml(pred)} <div> ${objs.map(this._objBlock.bind(this))} </div> </div> `; } byTypeBlock(byType: TypeToSubjs, typeUri: NamedNode) { const subjSet = byType.get(typeUri); const subjs: Array<NamedNode> = subjSet ? Array.from(subjSet) : []; subjs.sort(); const graphCells = new Map<string, Set<Term>>(); // [subj, pred] : objs const makeCellKey = (subj: NamedNode, pred: NamedNode) => subj.value + '|||' + pred.value; const preds = new Set<NamedNode>(); subjs.forEach((subj: NamedNode) => { this.graph.forEach((q: Quad) => { if (!Util.isNamedNode(q.predicate)) { throw new Error(); } preds.add(q.predicate as NamedNode); const cellKey = makeCellKey(subj, q.predicate as NamedNode); if (!graphCells.has(cellKey)) { graphCells.set(cellKey, new Set<Term>()); } graphCells.get(cellKey)!.add(q.object); }, subj, null, null, null); }); const predsList = Array.from(preds); predsList.splice(predsList.indexOf(rdf.type), 1); // also pull out label, which should be used on 1st column predsList.sort(); const thead = () => { const predColumnHead = (pred: NamedNode) => { return html`<th>${this.nodeDisplay.getHtml(pred)}</th>`; }; return html` <thead> <tr> <th></th> ${predsList.map(predColumnHead)} </tr> </thead>`; }; const instanceRow = (subj: NamedNode) => { const cell = (pred: NamedNode) => { const objs = graphCells.get(subj + '|||' + pred); if (!objs) { return html`<td></td>`; } const objsList = Array.from(objs); objsList.sort(); const draw = (obj: Term) => { return html`<div>${this.nodeDisplay.getHtml(obj)}</div>` }; return html`<td>${objsList.map(draw)}</td>`; }; return html` <tr> <td>${this.nodeDisplay.getHtml(subj)}</td> ${predsList.map(cell)} </tr> `; }; return html` <div>[icon] ${this.nodeDisplay.getHtml(typeUri)} resources</div> <div class="typeBlockScroll"> <table class="typeBlock"> ${thead()} ${subjs.map(instanceRow)} </table> </div> `; }; makeTemplate(): TemplateResult { const { byType, untyped } = groupByRdfType(this.graph); const typedSubjs = Array.from(byType.keys()); typedSubjs.sort(); const untypedSubjs = Array.from(untyped.values()); untypedSubjs.sort(); return html` <link rel="stylesheet" href="../src/streamed-graph.css"> <section> <h2> Current graph (<a href="${this.url}">${this.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((t: NamedNode) => this.byTypeBlock(byType, t))} <div class="spoGrid"> ${untypedSubjs.map(this._subjBlock.bind(this))} </div> </section> `; } }