Mercurial > code > home > repos > streamed-graph
view src/layout/Layout.ts @ 117:069c1f70afa5
cleanup
author | drewp@bigasterisk.com |
---|---|
date | Sat, 19 Mar 2022 17:45:01 -0700 |
parents | dd3325cc023e |
children | c2923b20bf5c |
line wrap: on
line source
// Organize graph data into tables (column orders, etc) for the view layer. import Immutable from "immutable"; // mostly using this for the builtin equals() testing, since NamedNode(x)!=NamedNode(x) import { NamedNode, Quad, Store, Term } from "n3"; import { rdf, rdfs } from "./namespaces"; import { uniqueSortedTerms, UriPairMap } from "./rdf_value"; import { ViewConfig } from "./ViewConfig"; type UriSet = Immutable.Set<NamedNode>; interface ColumnHeader { rdfType: NamedNode; pred: NamedNode; } export interface AlignedTable { columnHeaders: ColumnHeader[]; rowHeaders: NamedNode[]; rows: Term[][][]; } export interface PredRow { pred: NamedNode; objs: Term[]; } export interface SubjRow { subj: NamedNode; predRows: PredRow[]; } export interface FreeStatements { subjRows: SubjRow[]; } export interface LayoutResult { sections: (AlignedTable | FreeStatements)[]; } class AlignedTableBuilder { subjSet: UriSet = Immutable.Set(); predSet: UriSet = Immutable.Set(); cell = new UriPairMap(); constructor( public rdfType: NamedNode /* plus join types, sort instructions */ ) {} addQuad(q: Quad) { const subj = q.subject as NamedNode; const pred = q.predicate as NamedNode; this.subjSet = this.subjSet.add(subj); this.predSet = this.predSet.add(pred); this.cell.add(subj, pred, q.object); } _displayedPreds(): NamedNode[] { let preds = uniqueSortedTerms(this.predSet); preds = preds.filter((p) => { return !p.equals(rdf.type); }); const tagged = preds.map((p, i) => { if (p.equals(rdfs.label)) { i = -1; } return { sort: i, val: p }; }); tagged.sort((a, b) => { return a.sort - b.sort; }); preds = tagged.map((e) => e.val); return preds; } gotStatements(): boolean { return !this.subjSet.isEmpty(); } value(): AlignedTable { const subjs = uniqueSortedTerms(this.subjSet); const preds = this._displayedPreds(); const outputGrid: Term[][][] = []; for (let subj of subjs) { const row: Term[][] = []; preds.forEach((pred) => { const objs = this.cell.get(subj, pred); const uniq = uniqueSortedTerms(objs); row.push(uniq); }); outputGrid.push(row); } const headers = preds.map((pred) => { return { rdfType: this.rdfType, pred: pred }; }); return { columnHeaders: headers, rowHeaders: subjs, rows: outputGrid }; } } type SubjectTableBuilders = Immutable.Map< NamedNode<string>, AlignedTableBuilder[] >; function subjectsToTablesMap( graph: Store, tableBuilders: AlignedTableBuilder[] ): SubjectTableBuilders { let out: SubjectTableBuilders = Immutable.Map(); graph.forEach( (q: Quad) => { const s = q.subject as NamedNode; tableBuilders.forEach((tb) => { if (tb.rdfType.equals(q.object)) { let cur = out.get(s); if (cur === undefined) { cur = []; out = out.set(s, cur); } cur.push(tb); } }); }, null, rdf.type, null, null ); return out; } function freeStatmentsSection(stmts: Quad[]): FreeStatements { const subjs: NamedNode[] = []; stmts.forEach((q) => { subjs.push(q.subject as NamedNode); }); return { subjRows: uniqueSortedTerms(subjs).map((subj) => { const preds: NamedNode[] = []; let po = Immutable.Map<NamedNode, Term[]>(); stmts.forEach((q) => { if (q.subject.equals(subj)) { const p = q.predicate as NamedNode; preds.push(p); po = po.set(p, po.get(p, [])); po.get(p)?.push(q.object as Term); } }); const rows: PredRow[] = []; uniqueSortedTerms(preds).forEach((p) => { rows.push({ pred: p, objs: uniqueSortedTerms(po.get(p, [])) }); }); return { subj: subj, predRows: rows }; }), }; } // The description of how this page should look: sections, tables, etc. export class Layout { constructor(public viewConfig?: ViewConfig) {} _groupAllStatements( graph: Store, tablesWantingSubject: SubjectTableBuilders, ungrouped: Quad[] ) { graph.forEach( (q: Quad) => { const tables = tablesWantingSubject.get(q.subject as NamedNode); if (tables && tables.length) { tables.forEach((t: AlignedTableBuilder) => t.addQuad(q)); } else { ungrouped.push(q); } }, null, null, null, null ); } plan(graph: Store): LayoutResult { const ungrouped: Quad[] = []; const tableBuilders = this.viewConfig ? this.viewConfig.tables.map((t) => new AlignedTableBuilder(t.primary)) : []; const tablesWantingSubject = subjectsToTablesMap(graph, tableBuilders); this._groupAllStatements(graph, tablesWantingSubject, ungrouped); const res: LayoutResult = { sections: [] }; for (const t of tableBuilders) { if (t.gotStatements()) { res.sections.push(t.value()); } } if (ungrouped.length) { res.sections.push(freeStatmentsSection(ungrouped)); } return res; } }