Mercurial > code > home > repos > streamed-graph
diff 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 diff
--- a/src/Layout.ts Sat Mar 12 00:42:00 2022 -0800 +++ b/src/Layout.ts Sat Mar 12 22:41:43 2022 -0800 @@ -1,35 +1,21 @@ // 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 { - DataFactory, - NamedNode, - Quad, - Quad_Object, - Store, - Term, - Util, -} from "n3"; -import { ViewConfig } from "./ViewConfig"; - -const { namedNode } = DataFactory; - -// // import ns from 'n3/src/IRIs'; -// // const { rdf } = ns; +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 -if ((NamedNode.prototype as any).hashCode === undefined) { - (NamedNode.prototype as any).hashCode = () => 0; -} +(NamedNode.prototype as any).hashCode = () => 0; interface ColumnHeader { - rdfType?: NamedNode; // could be more than one column that introduces an rdf:type for a join + rdfType: NamedNode; pred: NamedNode; } -interface AlignedTable { +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 } @@ -40,102 +26,115 @@ 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) => { - ungrouped.push(q); + if (!subjectsToGatherSet.contains(q.subject as NamedNode) || !table) { + ungrouped.push(q); + } else { + table.addQuad(q); + } }, null, null, null, null ); - return { sections: [{ statements: ungrouped }] }; + const res: LayoutResult = { sections: [] }; + if (table) { + res.sections.push(table.value()); + } + res.sections.push({ statements: ungrouped }); + return res; } } -// function getType(graph: Store, subj: NamedNode): NamedNode | null { -// let subjType: NamedNode | null = null; - -// graph.forObjects( -// (o: Quad_Object) => { -// subjType = o as NamedNode; -// }, -// subj, -// rdf.type, -// null -// ); -// return subjType; -// } - -// // When there are multiple types, an arbitrary one is used. -// export function groupByRdfType( -// graph: Store -// ): { -// byType: TypeToSubjs; -// typesPresent: NamedNode[]; -// untypedSubjs: NamedNode[]; -// } { -// let byType: TypeToSubjs = Immutable.Map(); -// let untyped: UriSet = Immutable.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; - -// const subjType = getType(graph, subj); - -// if (subjType !== null) { -// // (subj, rdf:type, subjType) in graph -// const oldKeys = Array.from(byType.keys()); -// const oldVal = byType.get(subjType, Immutable.Set<NamedNode>()); -// const newVal = oldVal.add(subj); -// byType = byType.set(subjType, newVal); -// } else { -// untyped = untyped.add(subj); -// } -// }, -// null, -// null, -// null, -// null -// ); - -// const typesPresent = Array.from(byType.keys()); -// typesPresent.sort(); - -// const untypedSubjs = Array.from(untyped.values()); -// untypedSubjs.sort(); -// return { -// byType: byType, -// typesPresent: typesPresent, -// untypedSubjs: untypedSubjs, -// }; -// } - -// export function predsForSubj(graph: Store, typeUri: NamedNode): NamedNode[] { -// const predsSet: Set<NamedNode> = new Set(); -// graph.forEach( -// (q: Quad) => { -// predsSet.add(q.predicate as NamedNode); -// }, -// typeUri, -// null, -// null, -// null -// ); -// const preds = Array.from(predsSet.values()); -// preds.sort(); -// return preds; -// } - // interface ISP { // subj: NamedNode; // pred: NamedNode;