Mercurial > code > home > repos > streamed-graph
view src/Layout.ts @ 104:1aea03d306af
WIP Layout. tests are passing but they're a little wrong
author | drewp@bigasterisk.com |
---|---|
date | Sat, 12 Mar 2022 22:41:43 -0800 |
parents | f12feced00ce |
children |
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 } from "./namespaces"; import { TableDesc, ViewConfig } from "./ViewConfig"; type UriSet = Immutable.Set<NamedNode>; export type TypeToSubjs = Immutable.Map<NamedNode, UriSet>; // https://github.com/rdfjs/N3.js/issues/265 (NamedNode.prototype as any).hashCode = () => 0; interface ColumnHeader { rdfType: NamedNode; pred: NamedNode; } export interface AlignedTable { columnHeaders: ColumnHeader[]; rows: (Term | null)[][]; // each row is 1 wider than columnHeaders since the 1st element is the subject for that row } interface FreeStatements { statements: Quad[]; } export interface LayoutResult { sections: (AlignedTable | FreeStatements)[]; } class AlignedTableBuilder { columnPreds = Immutable.List<NamedNode>(); subjRowIndices = Immutable.Map<NamedNode, number>(); rows: (Term | null)[][] = []; constructor( public rdfType: NamedNode /* plus join types, sort instructions */ ) {} addQuad(q: Quad) { const pred = q.predicate as NamedNode; const omittedColumn = pred.equals(rdf.type); if (!this.columnPreds.contains(pred) && !omittedColumn) { this.columnPreds = this.columnPreds.push(pred); // this is putting cols in random order this.rows.forEach((r) => r.push(null)); } const predIndex = omittedColumn ? null : this.columnPreds.indexOf(pred); let rowIndex = this.subjRowIndices.get(q.subject as NamedNode); if (rowIndex === undefined) { const newRow = new Array(1 + this.columnPreds.size).fill(null); newRow[0] = q.subject; this.rows.push(newRow); rowIndex = this.rows.length - 1; this.subjRowIndices = this.subjRowIndices.set( q.subject as NamedNode, rowIndex ); } if (predIndex !== null) { this.rows[rowIndex][1 + predIndex] = q.object; } } value(): AlignedTable { this.rows.sort((a, b) => { const uriA = (a[0] as NamedNode).value, uriB = (b[0] as NamedNode).value; return uriA.localeCompare(uriB); }); const headers = this.columnPreds .map((pred) => { return { rdfType: this.rdfType, pred: pred }; }) .toArray(); return { columnHeaders: headers, rows: this.rows }; } } function findTypesNeededForTables(viewConfig?: ViewConfig): UriSet { const typesToGather: NamedNode[] = []; if (viewConfig) { viewConfig.tables.forEach((t: TableDesc) => { typesToGather.push(t.primary); }); } return Immutable.Set(typesToGather); } function findSubjectsWithTypes(graph: Store, typesToGatherSet: UriSet): UriSet { const subjectsToGather: NamedNode[] = []; graph.forEach( (q: Quad) => { if (typesToGatherSet.contains(q.object as NamedNode)) { subjectsToGather.push(q.subject as NamedNode); } }, null, rdf.type, null, null ); return Immutable.Set(subjectsToGather); } export class Layout { constructor(public viewConfig?: ViewConfig) {} plan(graph: Store): LayoutResult { const typesToGatherSet = findTypesNeededForTables(this.viewConfig); const subjectsToGatherSet = findSubjectsWithTypes(graph, typesToGatherSet); const ungrouped: Quad[] = []; const vc = this.viewConfig; const table = vc && vc.tables.length > 0 ? new AlignedTableBuilder(vc.tables[0].primary) : null; graph.forEach( (q: Quad) => { if (!subjectsToGatherSet.contains(q.subject as NamedNode) || !table) { ungrouped.push(q); } else { table.addQuad(q); } }, null, null, null, null ); const res: LayoutResult = { sections: [] }; if (table) { res.sections.push(table.value()); } res.sections.push({ statements: ungrouped }); return res; } } // interface ISP { // subj: NamedNode; // pred: NamedNode; // } // const SP = Immutable.Record<ISP>({ // subj: new NamedNode(""), // pred: new NamedNode(""), // }); // // One table of rows with a common rdf:type. // export class MultiSubjsTypeBlockLayout { // subjs: NamedNode[]; // preds: NamedNode[]; // graphCells: Immutable.Map<ISP, Immutable.Set<Term>>; // constructor(graph: Store, byType: TypeToSubjs, table: TableDesc) { // const subjSet = byType.get(table.primary); // this.subjs = subjSet ? Array.from(subjSet) : []; // this.subjs.sort(); // let preds = Immutable.Set<NamedNode>(); // this.graphCells = Immutable.Map<ISP, Immutable.Set<Term>>().withMutations( // (mutGraphCells) => { // this.subjs.forEach((subj: NamedNode) => { // graph.forEach( // (q: Quad) => { // if (!Util.isNamedNode(q.predicate)) { // throw new Error(); // } // const pred = q.predicate as NamedNode; // if (pred.equals(rdf.type)) { // // the whole block is labeled with the type // return; // } // preds = preds.add(pred); // const cellKey = this.makeCellKey(subj, pred); // mutGraphCells.set( // cellKey, // mutGraphCells.get(cellKey, Immutable.Set<Term>()).add(q.object) // ); // }, // subj, // null, // null, // null // ); // }); // } // ); // this.preds = Array.from(preds); // this.preds.splice(this.preds.indexOf(rdf.type), 1); // // also pull out label, which should be used on 1st column // this.preds.sort(); // } // makeCellKey(subj: NamedNode, pred: NamedNode): ISP { // return SP({ // subj: subj, // pred: pred, // }); // } // }