comparison src/layout/Layout.ts @ 118:c2923b20bf5c

support multi labels per column
author drewp@bigasterisk.com
date Sun, 20 Mar 2022 00:54:19 -0700
parents 069c1f70afa5
children 8715633f5213
comparison
equal deleted inserted replaced
117:069c1f70afa5 118:c2923b20bf5c
7 import { ViewConfig } from "./ViewConfig"; 7 import { ViewConfig } from "./ViewConfig";
8 8
9 type UriSet = Immutable.Set<NamedNode>; 9 type UriSet = Immutable.Set<NamedNode>;
10 10
11 interface ColumnHeader { 11 interface ColumnHeader {
12 rdfType: NamedNode; 12 rdfTypes: NamedNode[];
13 pred: NamedNode; 13 pred: NamedNode;
14 } 14 }
15 15
16 export interface AlignedTable { 16 export interface AlignedTable {
17 columnHeaders: ColumnHeader[]; 17 columnHeaders: ColumnHeader[];
38 } 38 }
39 39
40 class AlignedTableBuilder { 40 class AlignedTableBuilder {
41 subjSet: UriSet = Immutable.Set(); 41 subjSet: UriSet = Immutable.Set();
42 predSet: UriSet = Immutable.Set(); 42 predSet: UriSet = Immutable.Set();
43 subjsSeenWithPred: Immutable.Map<NamedNode, NamedNode[]> = Immutable.Map();
44 typesSeenForSubj: Immutable.Map<NamedNode, NamedNode[]> = Immutable.Map();
45
43 cell = new UriPairMap(); 46 cell = new UriPairMap();
44 constructor( 47 constructor(public primaryType: NamedNode, public joinTypes: NamedNode[]) {}
45 public rdfType: NamedNode /* plus join types, sort instructions */ 48
46 ) {} 49 showsType(rdfType: NamedNode): boolean {
50 return (
51 this.primaryType.equals(rdfType) ||
52 Immutable.Set<NamedNode>(this.joinTypes).has(rdfType)
53 );
54 }
47 55
48 addQuad(q: Quad) { 56 addQuad(q: Quad) {
49 const subj = q.subject as NamedNode; 57 const subj = q.subject as NamedNode;
50 const pred = q.predicate as NamedNode; 58 const pred = q.predicate as NamedNode;
51 this.subjSet = this.subjSet.add(subj); 59 this.subjSet = this.subjSet.add(subj);
52 this.predSet = this.predSet.add(pred); 60 this.predSet = this.predSet.add(pred);
53 this.cell.add(subj, pred, q.object); 61 this.cell.add(subj, pred, q.object);
62
63 if (pred.equals(rdf.type)) {
64 this.trackTypes(subj, q.object as NamedNode);
65 }
66 this.trackSubjs(subj, pred);
67 }
68
69 private trackTypes(subj: NamedNode<string>, rdfType: NamedNode) {
70 let cur = this.typesSeenForSubj.get(subj, undefined);
71 if (cur === undefined) {
72 cur = [];
73 this.typesSeenForSubj = this.typesSeenForSubj.set(subj, cur);
74 }
75 cur.push(rdfType);
76 }
77
78 private trackSubjs(subj: NamedNode, pred: NamedNode<string>) {
79 let cur = this.subjsSeenWithPred.get(pred, undefined);
80 if (cur === undefined) {
81 cur = [];
82 this.subjsSeenWithPred = this.subjsSeenWithPred.set(pred, cur);
83 }
84 cur.push(subj);
54 } 85 }
55 86
56 _displayedPreds(): NamedNode[] { 87 _displayedPreds(): NamedNode[] {
57 let preds = uniqueSortedTerms(this.predSet); 88 let preds = uniqueSortedTerms(this.predSet);
58 preds = preds.filter((p) => { 89 preds = preds.filter((p) => {
68 return a.sort - b.sort; 99 return a.sort - b.sort;
69 }); 100 });
70 preds = tagged.map((e) => e.val); 101 preds = tagged.map((e) => e.val);
71 return preds; 102 return preds;
72 } 103 }
104
73 gotStatements(): boolean { 105 gotStatements(): boolean {
74 return !this.subjSet.isEmpty(); 106 return !this.subjSet.isEmpty();
75 } 107 }
108
76 value(): AlignedTable { 109 value(): AlignedTable {
77 const subjs = uniqueSortedTerms(this.subjSet); 110 const subjs = uniqueSortedTerms(this.subjSet);
78 const preds = this._displayedPreds(); 111 const preds = this._displayedPreds();
79 const outputGrid: Term[][][] = []; 112 const outputGrid: Term[][][] = [];
80 for (let subj of subjs) { 113 for (let subj of subjs) {
85 row.push(uniq); 118 row.push(uniq);
86 }); 119 });
87 outputGrid.push(row); 120 outputGrid.push(row);
88 } 121 }
89 122
90 const headers = preds.map((pred) => { 123 const headers: ColumnHeader[] = preds.map((pred) => {
91 return { rdfType: this.rdfType, pred: pred }; 124 const subs = this.subjsSeenWithPred.get(pred, []);
125 const types: NamedNode[] = [];
126 subs.forEach((s) => {
127 this.typesSeenForSubj.get(s, []).forEach((t) => {
128 types.push(t);
129 });
130 });
131
132 return { rdfTypes: uniqueSortedTerms(types), pred: pred };
92 }); 133 });
93 return { columnHeaders: headers, rowHeaders: subjs, rows: outputGrid }; 134 return { columnHeaders: headers, rowHeaders: subjs, rows: outputGrid };
94 } 135 }
95 } 136 }
96 137
105 ): SubjectTableBuilders { 146 ): SubjectTableBuilders {
106 let out: SubjectTableBuilders = Immutable.Map(); 147 let out: SubjectTableBuilders = Immutable.Map();
107 graph.forEach( 148 graph.forEach(
108 (q: Quad) => { 149 (q: Quad) => {
109 const s = q.subject as NamedNode; 150 const s = q.subject as NamedNode;
151 const rdfType = q.object as NamedNode;
110 tableBuilders.forEach((tb) => { 152 tableBuilders.forEach((tb) => {
111 if (tb.rdfType.equals(q.object)) { 153 if (tb.showsType(rdfType)) {
112 let cur = out.get(s); 154 let cur = out.get(s, undefined);
113 if (cur === undefined) { 155 if (cur === undefined) {
114 cur = []; 156 cur = [];
115 out = out.set(s, cur); 157 out = out.set(s, cur);
116 } 158 }
117 cur.push(tb); 159 cur.push(tb);
163 ungrouped: Quad[] 205 ungrouped: Quad[]
164 ) { 206 ) {
165 graph.forEach( 207 graph.forEach(
166 (q: Quad) => { 208 (q: Quad) => {
167 const tables = tablesWantingSubject.get(q.subject as NamedNode); 209 const tables = tablesWantingSubject.get(q.subject as NamedNode);
210
168 if (tables && tables.length) { 211 if (tables && tables.length) {
169 tables.forEach((t: AlignedTableBuilder) => t.addQuad(q)); 212 tables.forEach((t: AlignedTableBuilder) => t.addQuad(q));
170 } else { 213 } else {
171 ungrouped.push(q); 214 ungrouped.push(q);
172 } 215 }
180 223
181 plan(graph: Store): LayoutResult { 224 plan(graph: Store): LayoutResult {
182 const ungrouped: Quad[] = []; 225 const ungrouped: Quad[] = [];
183 226
184 const tableBuilders = this.viewConfig 227 const tableBuilders = this.viewConfig
185 ? this.viewConfig.tables.map((t) => new AlignedTableBuilder(t.primary)) 228 ? this.viewConfig.tables.map(
229 (t) => new AlignedTableBuilder(t.primary, t.joins)
230 )
186 : []; 231 : [];
187 232
188 const tablesWantingSubject = subjectsToTablesMap(graph, tableBuilders); 233 const tablesWantingSubject = subjectsToTablesMap(graph, tableBuilders);
189 this._groupAllStatements(graph, tablesWantingSubject, ungrouped); 234 this._groupAllStatements(graph, tablesWantingSubject, ungrouped);
190 const res: LayoutResult = { sections: [] }; 235 const res: LayoutResult = { sections: [] };