Mercurial > code > home > repos > streamed-graph
changeset 103:f12feced00ce
WIP rewriting Layout
author | drewp@bigasterisk.com |
---|---|
date | Sat, 12 Mar 2022 00:42:00 -0800 |
parents | ab7dca42afbd |
children | 1aea03d306af |
files | src/Layout.test.ts src/Layout.ts src/fetchAndParse.ts src/namespaces.ts src/tabulate.test.ts src/tabulate.ts |
diffstat | 6 files changed, 383 insertions(+), 232 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Layout.test.ts Sat Mar 12 00:42:00 2022 -0800 @@ -0,0 +1,169 @@ +import { Layout, LayoutResult } from "./Layout"; +import { ViewConfig } from "./ViewConfig"; +import { + DataFactory, + Store, + Prefixes, + Parser, + Quad, + NamedNode, + Term, +} from "n3"; +import { EX, rdf } from "./namespaces"; +import Immutable from "immutable"; +import { n3Graph } from "./fetchAndParse"; +const { namedNode } = DataFactory; + +const twoStatements = async (): Promise<Store> => { + return n3Graph(` + @prefix : <http://example.com/> . + :g1 { + :a :b :c . + :d :e :f . + } + `); +}; + +const typedStatements = async (): Promise<Store> => { + return n3Graph(` + @prefix : <http://example.com/> . + :g1 { + :a a :T1 ; :color :red . + :b a :T1 ; :color :blue . + :c a :T1 . + :d a :T2 ; :size :big . + :e a :T1,:T2; :size :small + } + `); +}; +function G1(s: Term, p: Term, o: Term): Quad { + return new Quad(s, p, o, EX("g1")); +} + +describe("Layout", () => { + it("accepts a ViewConfig", async () => { + const vc = new ViewConfig(); + await vc.readFromGraph(` + @prefix ex: <http://example.com/> . + @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . + + <> a ex:View; rdfs:label "repos" .`); + const layout = new Layout(vc); + const lr = layout.plan(await twoStatements()); + }); + it("defaults to putting all triples in the ungrouped list", async () => { + const layout = new Layout(); + const lr = layout.plan(await twoStatements()); + expect(lr).toEqual({ + sections: [ + { + statements: [ + G1(EX("a"), EX("b"), EX("c")), + G1(EX("d"), EX("e"), EX("f")), + ], + }, + ], + }); + }); + describe("makes a table as requested by ViewConfig", () => { + let lr: LayoutResult; + + beforeAll(async () => { + const vc = new ViewConfig(); + await vc.readFromGraph(` + @prefix : <http://example.com/> . + @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . + + <> a :View; :table [ :primaryType :T1 ] .`); + const layout = new Layout(vc); + const lr = layout.plan(await typedStatements()); + expect(lr.sections).toHaveLength(2); + }); + it("puts the right type in the table", async () => { + expect(lr.sections[0]).toEqual({ + columnHeaders: [{ rdfType: EX("T1"), pred: EX("color") }], + rows: [ + [EX("a"), EX("red")], + [EX("b"), EX("blue")], + [EX("c"), null], + [EX("e"), null], + ], + }); + }); + it("leaves the rest ungrouped", async () => { + expect(lr.sections[1]).toEqual({ + statements: [ + G1(EX("d"), rdf.type, EX("T1")), + G1(EX("d"), EX("size"), EX("big")), + ], + }); + }); + }); + it("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", () => { + +// }); +// }); +// });
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Layout.ts Sat Mar 12 00:42:00 2022 -0800 @@ -0,0 +1,201 @@ +// 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; + +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; +} + +interface ColumnHeader { + rdfType?: NamedNode; // could be more than one column that introduces an rdf:type for a join + pred: NamedNode; +} +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)[]; +} + +export class Layout { + constructor(public viewConfig?: ViewConfig) {} + plan(graph: Store): LayoutResult { + const ungrouped: Quad[] = []; + + graph.forEach( + (q: Quad) => { + ungrouped.push(q); + }, + null, + null, + null, + null + ); + return { sections: [{ statements: ungrouped }] }; + } +} + +// 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; +// } +// 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, +// }); +// } +// }
--- a/src/fetchAndParse.ts Fri Mar 11 23:19:35 2022 -0800 +++ b/src/fetchAndParse.ts Sat Mar 12 00:42:00 2022 -0800 @@ -14,14 +14,17 @@ store = new Store(); } - const parser = new Parser({ format: "N3" }); - return new Promise((res, rej) => { + const parser = new Parser({ format: "TriG" }); + await new Promise((res, rej) => { parser.parse(n3, (error, quad: Quad, prefixes: Prefixes) => { + if (error) rej(error); if (quad) { store!.addQuad(quad); } else { - res(store!); + res(undefined); } }); }); + + return store; }
--- a/src/namespaces.ts Fri Mar 11 23:19:35 2022 -0800 +++ b/src/namespaces.ts Sat Mar 12 00:42:00 2022 -0800 @@ -1,5 +1,11 @@ -import { Util } from "n3"; +import { DataFactory, Util } from "n3"; +const { namedNode } = DataFactory; export const RDFS = Util.prefix("http://www.w3.org/2000/01/rdf-schema#"); export const RDF = Util.prefix("http://www.w3.org/1999/02/22-rdf-syntax-ns#"); export const EX = Util.prefix("http://example.com/"); + +export const rdf = { + type: namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), + }; + \ No newline at end of file
--- a/src/tabulate.test.ts Fri Mar 11 23:19:35 2022 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,66 +0,0 @@ -import { groupByRdfType } from "./tabulate"; -import { - Literal, - DataFactory, - Store, - Prefixes, - Parser, - Quad, - NamedNode, -} from "n3"; -import Immutable from "immutable"; -const { namedNode } = DataFactory; - -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")]); - }); -});
--- a/src/tabulate.ts Fri Mar 11 23:19:35 2022 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,162 +0,0 @@ -// 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 { TableDesc } from "./view_loader"; - -const { namedNode } = DataFactory; - -// // import ns from 'n3/src/IRIs'; -// // const { rdf } = ns; -export const rdf = { - type: namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), -}; - -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; -} - -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; -} -const SP = Immutable.Record<ISP>({ - subj: new NamedNode(""), - pred: new NamedNode(""), -}); - -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, - }); - } -}