# HG changeset patch # User drewp@bigasterisk.com # Date 1647735784 25200 # Node ID dd3325cc023e606168c6455fa1d524406f1f3e80 # Parent 84551452a9c9f6e18998d1ef02b6041396aaa880 multiple table support in Layout diff -r 84551452a9c9 -r dd3325cc023e src/layout/Layout.test.ts --- a/src/layout/Layout.test.ts Sat Mar 19 16:37:29 2022 -0700 +++ b/src/layout/Layout.test.ts Sat Mar 19 17:23:04 2022 -0700 @@ -41,6 +41,12 @@ const layout = new Layout(vc); const lr = layout.plan(await twoStatements()); }); + it("returns no sections for empty graph", () => { + const vc = new ViewConfig(); + const layout = new Layout(vc); + const lr = layout.plan(new Store()); + expect(lr.sections).toHaveLength(0); + }); it("defaults to putting all triples in the ungrouped list", async () => { const layout = new Layout(); const lr = layout.plan(await twoStatements()); @@ -106,71 +112,38 @@ }); }); }); + it("makes two tables", async () => { + const vc = new ViewConfig(); + await vc.readFromGraph(` + @prefix ex: . + @prefix rdfs: . + @prefix : . + + <> a :View; :table [ :primaryType :T1 ], [ :primaryType :T2 ] .`); + const layout = new Layout(vc); + const lr = layout.plan(await typedStatements()); + expect(lr.sections).toHaveLength(2); + expect(lr.sections[0]).toEqual({ + columnHeaders: [ + { rdfType: EX("T1"), pred: EX("color") }, + { rdfType: EX("T1"), pred: EX("size") }, + ], + rowHeaders: [EX("a"), EX("b"), EX("c"), EX("e")], + rows: [ + [[EX("red")], []], + [[EX("blue")], []], + [[], []], + [[], [EX("small")]], + ], + }); + expect(lr.sections[1]).toEqual({ + columnHeaders: [{ rdfType: EX("T2"), pred: EX("size") }], + rowHeaders: [EX("d"), EX("e")], + rows: [ + [[EX("big")]], // + [[EX("small")]], + ], + }); + }); it.skip("makes a table out of ungrouped triples with the same type", async () => {}); }); - -// describe("equality", () => { -// test("investigation of https://github.com/rdfjs/N3.js/issues/265", () => { -// const x = namedNode("x"); -// const x2 = namedNode("x"); -// // (NamedNode.prototype as any).hashCode = () => 0; -// // expect((x as any).hashCode()).toEqual((x2 as any).hashCode()) -// expect(x === x2).toBeFalsy(); -// expect(x == x2).toBeFalsy(); -// expect(x.equals(x2)).toBeTruthy(); -// let imap = Immutable.Map(); -// imap = imap.set(x, 11); -// imap = imap.set(x, 22); -// imap = imap.set(x2, 33); -// expect(imap.has(x)).toBeTruthy(); -// expect(imap.has(x2)).toBeTruthy(); -// expect(imap.size).toEqual(1); -// }); -// }); - -// describe("groupByRdfType", () => { -// test("finds multiple graphs", () => {}); -// test("works", async () => { -// const store = new Store(); - -// const parser = new Parser(); -// await new Promise((res, rej) => { -// parser.parse( -// `PREFIX : -// :rs1 a :Foo; :pred1 "obj1" . -// :rs2 a :Foo; :pred1 "obj2" . -// :rs3 a :Bar . -// :rs4 :pred1 "obj4" . -// `, -// (error, quad: Quad, prefixes: Prefixes) => { -// if (quad) { -// store.addQuad(quad); -// } else { -// res(undefined); -// } -// } -// ); -// }); -// const grouped = groupByRdfType(store); -// expect(Array.from(grouped.byType.keys())).toHaveLength(2); -// expect(grouped.byType.get(namedNode("urn:Foo"))).toEqual( -// Immutable.Set([namedNode("urn:rs1"), namedNode("urn:rs2")]) -// ); -// expect(grouped.byType.get(namedNode("urn:Bar"))).toEqual( -// Immutable.Set([namedNode("urn:rs3")]) -// ); -// expect(grouped.untypedSubjs).toEqual([namedNode("urn:rs4")]); -// }); - -// describe("MultiSubjsTypeBlockLayout", () => { -// test("gathers subjs", () => { - -// }); -// test("gathers preds", () => { - -// }); -// test("cells reports filled cells", () => { - -// }); -// }); -// }); diff -r 84551452a9c9 -r dd3325cc023e src/layout/Layout.ts --- a/src/layout/Layout.ts Sat Mar 19 16:37:29 2022 -0700 +++ b/src/layout/Layout.ts Sat Mar 19 17:23:04 2022 -0700 @@ -4,7 +4,7 @@ import { NamedNode, Quad, Store, Term } from "n3"; import { rdf, rdfs } from "./namespaces"; import { uniqueSortedTerms, UriPairMap } from "./rdf_value"; -import { TableDesc, ViewConfig } from "./ViewConfig"; +import { ViewConfig } from "./ViewConfig"; type UriSet = Immutable.Set; export type TypeToSubjs = Immutable.Map; @@ -71,7 +71,9 @@ 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(); @@ -93,32 +95,36 @@ } } -function findTypesNeededForTables(viewConfig?: ViewConfig): UriSet { - const typesToGather: NamedNode[] = []; - if (viewConfig) { - viewConfig.tables.forEach((t: TableDesc) => { - typesToGather.push(t.primary); - }); - } - return Immutable.Set(typesToGather); -} +type SubjectTableBuilders = Immutable.Map< + NamedNode, + AlignedTableBuilder[] +>; -function findSubjectsWithTypes(graph: Store, typesToGather: UriSet): UriSet { - const subjectsToGather: NamedNode[] = []; - const ft = typesToGather.toArray()[0]; +function subjectsToTablesMap( + graph: Store, + tableBuilders: AlignedTableBuilder[] +): SubjectTableBuilders { + let out: SubjectTableBuilders = Immutable.Map(); graph.forEach( (q: Quad) => { - if (q.object.equals(ft)) { - //typesToGather.has(q.object as NamedNode)) { - subjectsToGather.push(q.subject as NamedNode); - } + 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 Immutable.Set(subjectsToGather); + return out; } function freeStatmentsSection(stmts: Quad[]): FreeStatements { @@ -151,29 +157,16 @@ // The description of how this page should look: sections, tables, etc. export class Layout { constructor(public viewConfig?: ViewConfig) {} - plan(graph: Store): LayoutResult { - const typesToTable = findTypesNeededForTables(this.viewConfig); - - const subjectsToTable = findSubjectsWithTypes(graph, typesToTable); - const ungrouped: Quad[] = []; - const vc = this.viewConfig; - const table = - vc && vc.tables.length > 0 - ? new AlignedTableBuilder(vc.tables[0].primary) //todo multiple tables - : null; - + _groupAllStatements( + graph: Store, + tablesWantingSubject: SubjectTableBuilders, + ungrouped: Quad[] + ) { graph.forEach( (q: Quad) => { - let contains = false; - subjectsToTable.forEach((s) => { - if (s.equals(q.subject)) { - contains = true; - } - }); - - // if (subjectsToTable.has(q.subject as NamedNode) && table) { // not working - if (contains && table) { - table.addQuad(q); + const tables = tablesWantingSubject.get(q.subject as NamedNode); + if (tables && tables.length) { + tables.forEach((t: AlignedTableBuilder) => t.addQuad(q)); } else { ungrouped.push(q); } @@ -183,12 +176,25 @@ 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: [] }; - if (table) { - console.log("table value"); - res.sections.push(table.value()); + for (const t of tableBuilders) { + if (t.gotStatements()) { + res.sections.push(t.value()); + } } - res.sections.push(freeStatmentsSection(ungrouped)); + if (ungrouped.length) { + res.sections.push(freeStatmentsSection(ungrouped)); + } return res; } }