Mercurial > code > home > repos > streamed-graph
view src/graph_view.ts @ 94:a5f53d397526
view: pick types to show at top-level
author | drewp@bigasterisk.com |
---|---|
date | Wed, 12 Jan 2022 22:09:20 -0800 |
parents | 955cde1550c3 |
children | 4d19759d0d9a |
line wrap: on
line source
import { html, TemplateResult } from "lit"; import { DataFactory, Literal, NamedNode, Quad, Store, Term, Util } from "n3"; import { SuffixLabels } from "./suffixLabels"; import { groupByRdfType, MultiSubjsTypeBlockLayout, predsForSubj, TypeToSubjs, } from "./tabulate"; import { View } from "./view_loader"; const { namedNode } = DataFactory; // https://github.com/rdfjs/N3.js/issues/265 if ((Literal.prototype as any).hashCode === undefined) { (Literal.prototype as any).hashCode = () => 0; } if ((NamedNode.prototype as any).hashCode === undefined) { (NamedNode.prototype as any).hashCode = () => 0; } class NodeDisplay { labels: SuffixLabels; constructor(labels: SuffixLabels) { this.labels = labels; } render(n: Term | NamedNode): TemplateResult { if (Util.isLiteral(n)) { n = n as Literal; let dtPart: any = ""; if ( n.datatype && n.datatype.value != "http://www.w3.org/2001/XMLSchema#string" && // boring n.datatype.value != "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString" // boring ) { dtPart = html` ^^<span class="literalType"> ${this.render(n.datatype)} </span> `; } return html` <span class="literal">${n.value}${dtPart}</span> `; } if (Util.isNamedNode(n)) { n = n as 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; view: View; graph: Store; nodeDisplay: NodeDisplay; constructor(url: string, viewUrl: string, graph: Store) { this.url = url; this.view = new View(viewUrl); 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 ); } _subjPredObjsBlock(subj: NamedNode) { const columns = predsForSubj(this.graph, subj); return html` <div class="subject"> ${this.nodeDisplay.render(subj)} <!-- todo: special section for uri/type-and-icon/label/comment --> <div> ${columns.map((p) => { return this._predObjsBlock(subj, p); })} </div> </div> `; } _objCell(obj: Term) { return html` <div class="object"> ${this.nodeDisplay.render(obj)} <!-- indicate what source or graph said this stmt --> </div> `; } _predObjsBlock(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.render(pred)} <div>${objs.map(this._objCell.bind(this))}</div> </div> `; } _drawObj(obj: Term): TemplateResult { return html` <div>${this.nodeDisplay.render(obj)}</div> `; } _drawColumnHead(pred: NamedNode): TemplateResult { return html` <th>${this.nodeDisplay.render(pred)}</th> `; } _thead(layout: MultiSubjsTypeBlockLayout): TemplateResult { return html` <thead> <tr> <th></th> ${layout.preds.map(this._drawColumnHead.bind(this))} </tr> </thead> `; } _msbCell(layout: MultiSubjsTypeBlockLayout, subj: NamedNode) { return (pred: NamedNode): TemplateResult => { const objs = layout.graphCells.get(layout.makeCellKey(subj, pred)); if (!objs || !objs.size) { return html` <td></td> `; } const objsList = Array.from(objs); objsList.sort(); return html` <td>${objsList.map(this._drawObj.bind(this))}</td> `; }; } _instanceRow(layout: MultiSubjsTypeBlockLayout) { return (subj: NamedNode): TemplateResult => { return html` <tr> <td>${this.nodeDisplay.render(subj)}</td> ${layout.preds.map(this._msbCell(layout, subj))} </tr> `; }; } _multiSubjsTypeBlock(byType: TypeToSubjs, typeUri: NamedNode) { const layout = new MultiSubjsTypeBlockLayout(this.graph, byType, typeUri); return html` <div>[icon] ${this.nodeDisplay.render(typeUri)} type resources</div> <div class="typeBlockScroll"> <table class="typeBlock"> ${this._thead(layout)} ${layout.subjs.map(this._instanceRow(layout))} </table> </div> `; } async makeTemplate(): Promise<TemplateResult> { await this.view.ready; const { byType, typesPresent, untypedSubjs } = groupByRdfType(this.graph); let viewTitle = html` (no view)`; if (this.view.url) { viewTitle = html` using view <a href="${this.view.url}">${this.view.label()}</a>`; } const typesToShow = this.view.typesToShow(typesPresent); return html` <section> <h2> Current graph (<a href="${this.url}">${this.url}</a>)${viewTitle} </h2> <div> <!-- todo: graphs and provenance. These statements are all in the <span data-bind="html: $root.createCurie(graphUri())">...</span> graph.--> </div> ${typesToShow.map((t: NamedNode) => this._multiSubjsTypeBlock(byType, t) )} <div class="spoGrid"> ${untypedSubjs.map(this._subjPredObjsBlock.bind(this))} </div> </section> `; } }