Mercurial > code > home > repos > streamed-graph
diff src/layout/Layout.ts @ 122:2e8fa3fec0c8
support joining subjects into wider rows
author | drewp@bigasterisk.com |
---|---|
date | Sun, 20 Mar 2022 14:12:03 -0700 |
parents | 3584f24becf4 |
children | 5a1a79f54779 |
line wrap: on
line diff
--- a/src/layout/Layout.ts Sun Mar 20 14:10:56 2022 -0700 +++ b/src/layout/Layout.ts Sun Mar 20 14:12:03 2022 -0700 @@ -2,9 +2,10 @@ 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 { addToValues, multiColumnSort } from "./algorithm"; import { rdf, rdfs } from "./namespaces"; import { uniqueSortedTerms, UriPairMap } from "./rdf_value"; -import { ViewConfig } from "./ViewConfig"; +import { Link, ViewConfig } from "./ViewConfig"; type UriSet = Immutable.Set<NamedNode>; @@ -37,61 +38,18 @@ sections: (AlignedTable | FreeStatements)[]; } -function addToValues<K, V>( - imap: Immutable.Map<K, V[]>, - key: K, - newValue: V -): Immutable.Map<K, V[]> { - let cur = imap.get(key, undefined); - let ret = imap; - if (cur === undefined) { - cur = []; - ret = imap.set(key, cur); - } - cur.push(newValue); - return ret; -} - -function multiColumnSort<T>( - elems: T[], - makeSortCols: (elem: T, index: number) => Array<number | string> -): T[] { - const tagged = elems.map((p, i) => { - return { - sort: makeSortCols(p, i), - val: p, - }; - }); - tagged.sort((e1, e2) => { - let index = 0; - for (let k1 of e1.sort) { - const k2 = e2.sort[index]; - if (!Immutable.is(k1, k2)) { - if (typeof k1 === "number") { - if (typeof k2 === "number") { - return k1 - k2; - } else { - throw new Error(`${k1} vs ${k2}`); - } - } else { - return k1.localeCompare(k2 as string); - } - } - index++; - } - return 0; - }); - return tagged.map((e) => e.val); -} - class AlignedTableBuilder { subjSet: UriSet = Immutable.Set(); predSet: UriSet = Immutable.Set(); subjsSeenWithPred: Immutable.Map<NamedNode, NamedNode[]> = Immutable.Map(); typesSeenForSubj: Immutable.Map<NamedNode, NamedNode[]> = Immutable.Map(); - + aliases: Immutable.Map<NamedNode, NamedNode> = Immutable.Map(); cell = new UriPairMap(); - constructor(public primaryType: NamedNode, public joinTypes: NamedNode[]) {} + constructor( + public primaryType: NamedNode, + public joinTypes: NamedNode[], + public links: Link[] + ) {} showsType(rdfType: NamedNode): boolean { return ( @@ -100,26 +58,54 @@ ); } + // Considering this.links, call adder on more subjs that should be routed to this table. + detectLinks(graph: Store, adder: (linkedSubj: NamedNode) => void) { + this.links.forEach((link: Link) => { + graph.forEach( + (q: Quad) => { + const linkedSubj = q.object as NamedNode; + adder(linkedSubj); + this.linkSubjs(q.subject as NamedNode, linkedSubj); + }, + null, + link.pred, + null, + null + ); + }); + this.chainLinks(); + } + + // When you make a row for displayedSubj, also include linkedSubj's data + private linkSubjs(displayedSubj: NamedNode, linkedSubj: NamedNode) { + this.aliases = this.aliases.set(linkedSubj, displayedSubj); + } + + // After this.aliases is built; shorten {b: a, c:b} to {b: a, c: a} + private chainLinks() { + for (let alias of this.aliases.keys()) { + let x = this.aliases.get(alias)!; + while (this.aliases.has(x)) { + x = this.aliases.get(x)!; + } + this.aliases = this.aliases.set(alias, x); + } + } + + // Stream in quads that belong to this table (caller has to work out that + // part), then call value(). addQuad(q: Quad) { - const subj = q.subject as NamedNode; + const unaliasedSubj = q.subject as NamedNode; + const aliasedSubj = this.aliases.get(unaliasedSubj, unaliasedSubj); const pred = q.predicate as NamedNode; - this.subjSet = this.subjSet.add(subj); + this.subjSet = this.subjSet.add(aliasedSubj); this.predSet = this.predSet.add(pred); - this.cell.add(subj, pred, q.object); + this.cell.add(aliasedSubj, pred, q.object); if (pred.equals(rdf.type)) { - this.trackTypes(subj, q.object as NamedNode); + this.trackTypes(unaliasedSubj, q.object as NamedNode); } - this.trackSubjs(subj, pred); - } - - private trackTypes(subj: NamedNode<string>, rdfType: NamedNode) { - let cur = this.typesSeenForSubj.get(subj, undefined); - if (cur === undefined) { - cur = []; - this.typesSeenForSubj = this.typesSeenForSubj.set(subj, cur); - } - cur.push(rdfType); + this.trackSubjs(unaliasedSubj, pred); } private trackTypes(unaliasedSubj: NamedNode<string>, rdfType: NamedNode) { @@ -138,10 +124,11 @@ ); } - _displayedPreds(): NamedNode[] { + private displayedPreds(): NamedNode[] { let preds = uniqueSortedTerms(this.predSet); preds = preds.filter((p) => { if (p.equals(rdf.type)) return false; + if (this.links.filter((l) => l.pred.equals(p)).length) return false; return true; }); preds = multiColumnSort(preds, (elem: NamedNode, index: number) => { @@ -172,10 +159,8 @@ return uniqueSortedTerms(types); } - value(): AlignedTable { - const subjs = uniqueSortedTerms(this.subjSet); - const preds = this._displayedPreds(); - const outputGrid: Term[][][] = []; + private gatherOutputGrid(subjs: NamedNode[], preds: NamedNode[]): Term[][][] { + const out: Term[][][] = []; for (let subj of subjs) { const row: Term[][] = []; preds.forEach((pred) => { @@ -183,13 +168,20 @@ const uniq = uniqueSortedTerms(objs); row.push(uniq); }); - outputGrid.push(row); + out.push(row); } + return out; + } - const headers: ColumnHeader[] = preds.map((pred) => { + value(): AlignedTable { + const subjs = uniqueSortedTerms(this.subjSet); + const preds = this.displayedPreds(); + const outputGrid: Term[][][] = this.gatherOutputGrid(subjs, preds); + + const colHeaders: ColumnHeader[] = preds.map((pred) => { return { rdfTypes: this.typesSeenWithPred(pred), pred: pred }; }); - return { columnHeaders: headers, rowHeaders: subjs, rows: outputGrid }; + return { columnHeaders: colHeaders, rowHeaders: subjs, rows: outputGrid }; } } @@ -218,6 +210,13 @@ null, null ); + + tableBuilders.forEach((tb) => { + tb.detectLinks(graph, (linkedSubj: NamedNode) => { + out = addToValues(out, linkedSubj, tb); + }); + }); + return out; } @@ -251,7 +250,7 @@ export class Layout { constructor(public viewConfig?: ViewConfig) {} - _groupAllStatements( + private groupAllStatements( graph: Store, tablesWantingSubject: SubjectTableBuilders, ungrouped: Quad[] @@ -273,26 +272,31 @@ ); } - plan(graph: Store): LayoutResult { - const ungrouped: Quad[] = []; - - const tableBuilders = this.viewConfig - ? this.viewConfig.tables.map( - (t) => new AlignedTableBuilder(t.primary, t.joins) - ) - : []; - - const tablesWantingSubject = subjectsToTablesMap(graph, tableBuilders); - this._groupAllStatements(graph, tablesWantingSubject, ungrouped); - const res: LayoutResult = { sections: [] }; + private generateSections( + tableBuilders: AlignedTableBuilder[], + ungrouped: Quad[] + ): (AlignedTable | FreeStatements)[] { + const sections = []; for (const t of tableBuilders) { if (t.gotStatements()) { - res.sections.push(t.value()); + sections.push(t.value()); } } if (ungrouped.length) { - res.sections.push(freeStatmentsSection(ungrouped)); + sections.push(freeStatmentsSection(ungrouped)); } - return res; + return sections; + } + + plan(graph: Store): LayoutResult { + const vcTables = this.viewConfig ? this.viewConfig.tables : []; + const tableBuilders = vcTables.map( + (t) => new AlignedTableBuilder(t.primary, t.joins, t.links) + ); + + const tablesWantingSubject = subjectsToTablesMap(graph, tableBuilders); + const ungrouped: Quad[] = []; + this.groupAllStatements(graph, tablesWantingSubject, ungrouped); + return { sections: this.generateSections(tableBuilders, ungrouped) }; } }