changeset 116:dd3325cc023e

multiple table support in Layout
author drewp@bigasterisk.com
date Sat, 19 Mar 2022 17:23:04 -0700
parents 84551452a9c9
children 069c1f70afa5
files src/layout/Layout.test.ts src/layout/Layout.ts
diffstat 2 files changed, 89 insertions(+), 110 deletions(-) [+]
line wrap: on
line diff
--- 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: <http://example.com/> .
+      @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+      @prefix : <http://example.com/> .
+
+      <> 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 : <urn:>
-//         :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", () => {
-
-//     });
-//   });
-// });
--- 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<NamedNode>;
 export type TypeToSubjs = Immutable.Map<NamedNode, UriSet>;
@@ -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<string>,
+  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;
   }
 }