Mercurial > code > home > repos > streamed-graph
diff src/render/graph_view.ts @ 106:2468f2227d22
make src/layout/ and src/render/ separation
author | drewp@bigasterisk.com |
---|---|
date | Sun, 13 Mar 2022 22:00:30 -0700 |
parents | src/graph_view.ts@26c55d5d5202 |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/render/graph_view.ts Sun Mar 13 22:00:30 2022 -0700 @@ -0,0 +1,196 @@ +import { html, TemplateResult } from "lit"; +import { DataFactory, Literal, NamedNode, Quad, Store, Term } from "n3"; +import { NodeDisplay } from "./NodeDisplay"; +import { SuffixLabels } from "../layout/suffixLabels"; +import { Layout } from "../layout/Layout"; +import { TableDesc, ViewConfig } from "../layout/ViewConfig"; + +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; +} +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(this.graph, labels); + + if (this.view.graph) { + this._addLabelsForAllTerms(this.view.graph, labels); + } + this.nodeDisplay = new NodeDisplay(labels); + } + + _addLabelsForAllTerms(graph: Store, labels: SuffixLabels) { + console.log("_addLabelsForAllTerms"); + + 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, table: TableDesc) { + const layout = new MultiSubjsTypeBlockLayout(this.graph, byType, table); + + let typeNames = [html`${this.nodeDisplay.render(table.primary)}`]; + if (table.joins) { + typeNames.push(html` joined with [`); + for (let j of table.joins) { + typeNames.push(html`${this.nodeDisplay.render(j)}`); + } + typeNames.push(html`]`); + } + + return html` + <div>[icon] Resources of type ${typeNames}</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 tables = this.view.toplevelTables(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> + ${tables.map((t: TableDesc) => this._multiSubjsTypeBlock(byType, t))} + <div class="spoGrid"> + ${untypedSubjs.map(this._subjPredObjsBlock.bind(this))} + </div> + </section> + `; + } +}