Mercurial > code > home > repos > streamed-graph
diff src/elements/graph-view/GraphView.ts @ 128:5a1a79f54779
big rewrite
author | drewp@bigasterisk.com |
---|---|
date | Fri, 05 May 2023 21:26:36 -0700 |
parents | src/render/GraphView.ts@c2923b20bf5c |
children | cf642d395be4 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/elements/graph-view/GraphView.ts Fri May 05 21:26:36 2023 -0700 @@ -0,0 +1,242 @@ +import Immutable from "immutable"; +import { LitElement, PropertyValues, TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import { NamedNode, Quad, Store, Term } from "n3"; +import { MultiStore } from "../../MultiStore"; +import { Patch } from "../../Patch"; +import { + AlignedTable, + FreeStatements, + Layout, + PredRow, + SubjRow, +} from "../../layout/Layout"; +import { ViewConfig } from "../../layout/ViewConfig"; +import { uniqueSortedTerms } from "../../layout/rdf_value"; +import { SuffixLabels } from "../../layout/suffixLabels"; +import { graphViewStyle, pageStyle } from "../../style"; +import { NodeDisplay } from "./NodeDisplay"; +type UriSet = Immutable.Set<NamedNode>; + +@customElement("graph-view") +export class GraphView extends LitElement { + @property() graph: MultiStore | null = null; + @property() viewEl: HTMLElement | null = null; + + viewConfig: ViewConfig | null = null; + + nodeDisplay: NodeDisplay | null = null; + constructor() { + super(); + } + + static styles = [pageStyle, graphViewStyle]; + update(changedProperties: PropertyValues) { + if (changedProperties.has("graph") && this.graph) { + // const viewUri = new NamedNode(this.viewEl?.getAttribute('uri')) + const viewUri = new NamedNode(new URL("#view", document.baseURI).href); + this.viewConfig = new ViewConfig(this.graph, viewUri); + + // "when viewconfig is updated..." + setTimeout(()=>this.requestUpdate(), 1000) + + this.graph.graphChanged.subscribe(this.onChange?.bind(this)); + } + super.update(changedProperties); + } + + onChange(p: Patch) { + this.requestUpdate(); + } + + render() { + if (!this.graph) { + return; + } + return this.makeTemplate(this.graph); + } + + makeTemplate(graph: MultiStore): TemplateResult { + if (!this.viewConfig) { + throw new Error(); + } + const layout = new Layout(this.viewConfig); + const lr = layout.plan(graph); + + const labels = new SuffixLabels(); + this._addLabelsForAllTerms(graph, labels); + labels.planDisplayForNode(this.viewConfig.viewRoot); // todo shoudltn be needed + this.nodeDisplay = new NodeDisplay(labels); + let viewTitle = html` (no view)`; + viewTitle = html` using view + <a href="${this.viewConfig.viewRoot.value}" + >{this.nodeDisplay.render(this.viewConfig.viewRoot)}</a + >`; + return html` + <section> + <h2>View: ${viewTitle}</h2> + <div> + <!-- todo: graphs and provenance. + These statements are all in the + <span data-bind="html: $root.createCurie(graphUri())">...</span> graph.--> + </div> + ${lr.sections.map(this._renderSection.bind(this))} + </section> + `; + } + + _addLabelsForAllTerms(graph: Store, labels: SuffixLabels) { + 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 + ); + } + + _renderSection(section: AlignedTable | FreeStatements) { + if ((section as any).columnHeaders) { + return this._renderAlignedTable(section as AlignedTable); + } else { + return this._renderFreeStatements(section as FreeStatements); + } + } + + _renderAlignedTable(section: AlignedTable): TemplateResult { + const nodeDisplay = this.nodeDisplay; + if (!nodeDisplay) throw new Error(); + const tableTypes: NamedNode[][] = []; + const typeHeads: TemplateResult[] = []; + const heads: TemplateResult[] = []; + for (let ch of section.columnHeaders) { + const colSpan = 1; //todo + typeHeads.push( + html`<th colspan="${colSpan}"> + ${ch.rdfTypes.map((n) => nodeDisplay.render(n))} + </th>` + ); + + tableTypes.push(ch.rdfTypes); + heads.push(html`<th>${nodeDisplay.render(ch.pred)}</th>`); + } + + const cells = []; + + for (let rowIndex in section.rows) { + const headerCol = nodeDisplay.render(section.rowHeaders[rowIndex]); + const bodyCols = []; + for (let cellObjs of section.rows[rowIndex]) { + const display = cellObjs.map( + (t) => html`<div>${nodeDisplay.render(t)}</div>` + ); + bodyCols.push(html`<td>${display}</td>`); + } + cells.push( + html`<tr> + <th>${headerCol}</th> + ${bodyCols} + </tr>` + ); + } + const tableTypesUnique = uniqueSortedTerms(tableTypes.flat()); + const typesDisplay = html`${tableTypesUnique.length == 1 ? "type" : "types"} + ${tableTypesUnique.map((n) => nodeDisplay.render(n))}`; + + return html` + <div>[icon] Resources of ${typesDisplay}</div> + <div class="typeBlockScroll"> + <table class="typeBlock"> + <thead> + <tr> + <th></th> + ${typeHeads} + </tr> + <tr> + <th>Subject</th> + ${heads} + </tr> + </thead> + <tbody> + ${cells} + </tbody> + </table> + </div> + `; + } + + _renderFreeStatements(section: FreeStatements): TemplateResult { + const subjects: NamedNode[] = []; + let subjPreds = Immutable.Map<NamedNode, UriSet>(); + + return html`<div class="spoGrid"> + grid has rowcount ${section.subjRows.length} + ${section.subjRows.map(this._subjPredObjsBlock.bind(this))} + </div>`; + } + + _subjPredObjsBlock(row: SubjRow): TemplateResult { + return html` + <div class="subject"> + ${this.nodeDisplay?.render(row.subj)} + <!-- todo: special section for uri/type-and-icon/label/comment --> + <div>${row.predRows.map(this._predObjsBlock.bind(this))}</div> + </div> + `; + } + + _predObjsBlock(row: PredRow): TemplateResult { + return html` + <div class="predicate"> + ${this.nodeDisplay?.render(row.pred)} + <div>${row.objs.map(this._objCell.bind(this))}</div> + </div> + `; + } + + _objCell(obj: Term): TemplateResult { + return html` + <div class="object"> + ${this.nodeDisplay?.render(obj)} + <!-- indicate what source or graph said this stmt --> + </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> `; + } + + // 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> + // `; + // } +} + +declare global { + interface HTMLElementTagNameMap { + "graph-view": GraphView; + } +}