Mercurial > code > home > repos > streamed-graph
diff src/graph_view.ts @ 88:ac7ad087d474
graph view rewrites and fixes for the multi-subject table
author | drewp@bigasterisk.com |
---|---|
date | Tue, 30 Nov 2021 00:14:37 -0800 |
parents | 067d66a45a51 |
children | 955cde1550c3 |
line wrap: on
line diff
--- a/src/graph_view.ts Thu Nov 25 18:52:07 2021 -0800 +++ b/src/graph_view.ts Tue Nov 30 00:14:37 2021 -0800 @@ -1,87 +1,42 @@ -import { html, TemplateResult } from 'lit'; -import { DataFactory, Literal, Store, NamedNode, Quad, Quad_Object, Term, Util } from 'n3'; - -import { SuffixLabels } from './suffixLabels'; +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"; const { namedNode } = DataFactory; -// // 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>>; -// When there are multiple types, an arbitrary one is used. -function groupByRdfType( - graph: Store -): { 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_Object) => { - subjType = o 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 }; +// 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; } - getHtml(n: Term | NamedNode): TemplateResult { + render(n: Term | NamedNode): TemplateResult { if (Util.isLiteral(n)) { n = n as Literal; let dtPart: any = ""; - if (n.datatype) { + 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.getHtml(n.datatype)} - </span> + ^^<span class="literalType"> ${this.render(n.datatype)} </span> `; } - return html` - <span class="literal">${n.value}${dtPart}</span> - `; + return html` <span class="literal">${n.value}${dtPart}</span> `; } if (Util.isNamedNode(n)) { @@ -92,7 +47,7 @@ ["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:"] + ["http://www.w3.org/2001/XMLSchema#", "xsd:"], ]) { if (uriValue.startsWith(long)) { uriValue = short + uriValue.substr(long.length); @@ -108,14 +63,10 @@ uriValue = dn; } - return html` - <a class="graphUri" href="${n.value}">${uriValue}</a> - `; + return html` <a class="graphUri" href="${n.value}">${uriValue}</a> `; } - return html` - [${n.termType} ${n.value}] - `; + return html` [${n.termType} ${n.value}] `; } } @@ -155,42 +106,31 @@ ); } - _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(); + _subjPredObjsBlock(subj: NamedNode) { + const columns = predsForSubj(this.graph, subj); return html` <div class="subject"> - ${this.nodeDisplay.getHtml(subj)} + ${this.nodeDisplay.render(subj)} <!-- todo: special section for uri/type-and-icon/label/comment --> <div> - ${preds.map(p => { - return this._predBlock(subj, p); + ${columns.map((p) => { + return this._predObjsBlock(subj, p); })} </div> </div> `; } - _objBlock(obj: Term) { + _objCell(obj: Term) { return html` <div class="object"> - ${this.nodeDisplay.getHtml(obj)} + ${this.nodeDisplay.render(obj)} <!-- indicate what source or graph said this stmt --> </div> `; } - _predBlock(subj: NamedNode, pred: NamedNode) { + _predObjsBlock(subj: NamedNode, pred: NamedNode) { const objsSet = new Set<Term>(); this.graph.forEach( (q: Quad) => { @@ -205,109 +145,69 @@ objs.sort(); return html` <div class="predicate"> - ${this.nodeDisplay.getHtml(pred)} - <div> - ${objs.map(this._objBlock.bind(this))} - </div> + ${this.nodeDisplay.render(pred)} + <div>${objs.map(this._objCell.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>(); + _drawObj(obj: Term): TemplateResult { + return html` <div>${this.nodeDisplay.render(obj)}</div> `; + } - 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(); + _drawColumnHead(pred: NamedNode): TemplateResult { + return html` <th>${this.nodeDisplay.render(pred)}</th> `; + } - 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> - `; - }; + _thead(layout: MultiSubjsTypeBlockLayout): TemplateResult { + return html` + <thead> + <tr> + <th></th> + ${layout.preds.map(this._drawColumnHead.bind(this))} + </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> - `; - }; + _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.getHtml(subj)}</td> - ${predsList.map(cell)} + <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.getHtml(typeUri)} resources</div> + <div>[icon] ${this.nodeDisplay.render(typeUri)} type resources</div> <div class="typeBlockScroll"> <table class="typeBlock"> - ${thead()} ${subjs.map(instanceRow)} + ${this._thead(layout)} ${layout.subjs.map(this._instanceRow(layout))} </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(); + const { byType, typedSubjs, untypedSubjs } = groupByRdfType(this.graph); return html` <section> @@ -317,9 +217,11 @@ These statements are all in the <span data-bind="html: $root.createCurie(graphUri())">...</span> graph.--> </div> - ${typedSubjs.map((t: NamedNode) => this.byTypeBlock(byType, t))} + ${typedSubjs.map((t: NamedNode) => + this._multiSubjsTypeBlock(byType, t) + )} <div class="spoGrid"> - ${untypedSubjs.map(this._subjBlock.bind(this))} + ${untypedSubjs.map(this._subjPredObjsBlock.bind(this))} </div> </section> `;