Mercurial > code > home > repos > streamed-graph
comparison src/render/GraphView.ts @ 108:5e6840229a05
rewrite freeStatements rendering to put more planning in layout
author | drewp@bigasterisk.com |
---|---|
date | Fri, 18 Mar 2022 11:57:38 -0700 |
parents | 042bd3361339 |
children | cbcd82d21356 |
comparison
equal
deleted
inserted
replaced
107:042bd3361339 | 108:5e6840229a05 |
---|---|
1 import Immutable from "immutable"; | |
1 import { html, TemplateResult } from "lit"; | 2 import { html, TemplateResult } from "lit"; |
2 import { DataFactory, Literal, NamedNode, Quad, Store, Term } from "n3"; | 3 import { DataFactory, Literal, NamedNode, Quad, Store, Term } from "n3"; |
3 import { NodeDisplay } from "./NodeDisplay"; | 4 import { NodeDisplay } from "./NodeDisplay"; |
4 import { SuffixLabels } from "../layout/suffixLabels"; | 5 import { SuffixLabels } from "../layout/suffixLabels"; |
5 import { Layout } from "../layout/Layout"; | 6 import { AlignedTable, FreeStatements, Layout, PredRow, SubjRow } from "../layout/Layout"; |
6 import { TableDesc, ViewConfig } from "../layout/ViewConfig"; | 7 import { TableDesc, ViewConfig } from "../layout/ViewConfig"; |
8 import { uniqueSortedTerms } from "../layout/rdf_value"; | |
7 | 9 |
8 const { namedNode } = DataFactory; | 10 const { namedNode } = DataFactory; |
9 | 11 |
12 type UriSet = Immutable.Set<NamedNode>; | |
13 const emptyUriSet = Immutable.Set<NamedNode>(); | |
10 // https://github.com/rdfjs/N3.js/issues/265 | 14 // https://github.com/rdfjs/N3.js/issues/265 |
11 if ((Literal.prototype as any).hashCode === undefined) { | 15 if ((Literal.prototype as any).hashCode === undefined) { |
12 (Literal.prototype as any).hashCode = () => 0; | 16 (Literal.prototype as any).hashCode = () => 0; |
13 } | 17 } |
14 if ((NamedNode.prototype as any).hashCode === undefined) { | 18 if ((NamedNode.prototype as any).hashCode === undefined) { |
15 (NamedNode.prototype as any).hashCode = () => 0; | 19 (NamedNode.prototype as any).hashCode = () => 0; |
16 } | 20 } |
17 export class GraphView { | 21 export class GraphView { |
18 url: string; | 22 nodeDisplay!: NodeDisplay; |
19 view: View; | 23 constructor( |
20 graph: Store; | 24 public dataSourceUrls: string[], |
21 nodeDisplay: NodeDisplay; | 25 public graph: Store, |
22 constructor(url: string, viewUrl: string, graph: Store) { | 26 public viewConfig?: ViewConfig |
23 this.url = url; | 27 ) {} |
24 this.view = new View(viewUrl); | 28 |
25 this.graph = graph; | 29 async makeTemplate(): Promise<TemplateResult> { |
30 const layout = new Layout(this.viewConfig); | |
31 const lr = layout.plan(this.graph); | |
26 | 32 |
27 const labels = new SuffixLabels(); | 33 const labels = new SuffixLabels(); |
28 this._addLabelsForAllTerms(this.graph, labels); | 34 this._addLabelsForAllTerms(this.graph, labels); |
29 | 35 |
30 this.view.ready.then(() => { | |
31 this._addLabelsForAllTerms(this.view.graph, labels); | |
32 }); | |
33 this.nodeDisplay = new NodeDisplay(labels); | 36 this.nodeDisplay = new NodeDisplay(labels); |
37 let viewTitle = html` (no view)`; | |
38 if (this.viewConfig?.url) { | |
39 viewTitle = html` using view | |
40 <a href="${this.viewConfig.url}">${this.viewConfig.label()}</a>`; | |
41 } | |
42 // const tables = this.view.toplevelTables(typesPresent); | |
43 return html` | |
44 <section> | |
45 <h2> | |
46 Current graph (<a href="${this.dataSourceUrls[0]}" | |
47 >${this.dataSourceUrls[0]}</a | |
48 >)${viewTitle} | |
49 </h2> | |
50 <div> | |
51 <!-- todo: graphs and provenance. | |
52 These statements are all in the | |
53 <span data-bind="html: $root.createCurie(graphUri())">...</span> graph.--> | |
54 </div> | |
55 ${lr.sections.map(this._renderSection.bind(this))} | |
56 </section> | |
57 `; | |
34 } | 58 } |
35 | 59 |
36 _addLabelsForAllTerms(graph: Store, labels: SuffixLabels) { | 60 _addLabelsForAllTerms(graph: Store, labels: SuffixLabels) { |
37 graph.forEach( | 61 graph.forEach( |
38 (q: Quad) => { | 62 (q: Quad) => { |
53 null, | 77 null, |
54 null, | 78 null, |
55 null | 79 null |
56 ); | 80 ); |
57 } | 81 } |
82 _renderSection(section: AlignedTable | FreeStatements) { | |
83 if ((section as any).columnHeaders) { | |
84 return this._renderAlignedTable(section as AlignedTable); | |
85 } else { | |
86 return this._renderFreeStatements(section as FreeStatements); | |
87 } | |
88 } | |
58 | 89 |
59 _subjPredObjsBlock(subj: NamedNode) { | 90 _renderAlignedTable(section: AlignedTable): TemplateResult { |
60 const columns = predsForSubj(this.graph, subj); | 91 return html`aligned table section`; |
92 } | |
93 | |
94 _renderFreeStatements(section: FreeStatements): TemplateResult { | |
95 const subjects: NamedNode[] = []; | |
96 let subjPreds = Immutable.Map<NamedNode, UriSet>(); | |
97 | |
98 return html`<div class="spoGrid"> | |
99 grid has rowcount ${section.subjRows.length} | |
100 ${section.subjRows.map(this._subjPredObjsBlock.bind(this))} | |
101 </div>`; | |
102 } | |
103 | |
104 _subjPredObjsBlock( row: SubjRow ): TemplateResult { | |
61 return html` | 105 return html` |
62 <div class="subject"> | 106 <div class="subject"> |
63 ${this.nodeDisplay.render(subj)} | 107 ${this.nodeDisplay.render(row.subj)} |
64 <!-- todo: special section for uri/type-and-icon/label/comment --> | 108 <!-- todo: special section for uri/type-and-icon/label/comment --> |
65 <div> | 109 <div> |
66 ${columns.map((p) => { | 110 ${row.predRows.map(this._predObjsBlock.bind(this))} |
67 return this._predObjsBlock(subj, p); | |
68 })} | |
69 </div> | 111 </div> |
70 </div> | 112 </div> |
71 `; | 113 `; |
72 } | 114 } |
73 | 115 |
74 _objCell(obj: Term) { | 116 _predObjsBlock(row: PredRow): TemplateResult { |
117 return html` | |
118 <div class="predicate"> | |
119 ${this.nodeDisplay.render(row.pred)} | |
120 <div>${row.objs.map(this._objCell.bind(this))}</div> | |
121 </div> | |
122 `; | |
123 } | |
124 | |
125 _objCell(obj: Term): TemplateResult { | |
75 return html` | 126 return html` |
76 <div class="object"> | 127 <div class="object"> |
77 ${this.nodeDisplay.render(obj)} | 128 ${this.nodeDisplay.render(obj)} |
78 <!-- indicate what source or graph said this stmt --> | 129 <!-- indicate what source or graph said this stmt --> |
79 </div> | |
80 `; | |
81 } | |
82 | |
83 _predObjsBlock(subj: NamedNode, pred: NamedNode) { | |
84 const objsSet = new Set<Term>(); | |
85 this.graph.forEach( | |
86 (q: Quad) => { | |
87 objsSet.add(q.object); | |
88 }, | |
89 subj, | |
90 pred, | |
91 null, | |
92 null | |
93 ); | |
94 const objs = Array.from(objsSet.values()); | |
95 objs.sort(); | |
96 return html` | |
97 <div class="predicate"> | |
98 ${this.nodeDisplay.render(pred)} | |
99 <div>${objs.map(this._objCell.bind(this))}</div> | |
100 </div> | 130 </div> |
101 `; | 131 `; |
102 } | 132 } |
103 | 133 |
104 _drawObj(obj: Term): TemplateResult { | 134 _drawObj(obj: Term): TemplateResult { |
107 | 137 |
108 _drawColumnHead(pred: NamedNode): TemplateResult { | 138 _drawColumnHead(pred: NamedNode): TemplateResult { |
109 return html` <th>${this.nodeDisplay.render(pred)}</th> `; | 139 return html` <th>${this.nodeDisplay.render(pred)}</th> `; |
110 } | 140 } |
111 | 141 |
112 _thead(layout: MultiSubjsTypeBlockLayout): TemplateResult { | 142 // _thead(layout: MultiSubjsTypeBlockLayout): TemplateResult { |
113 return html` | 143 // return html` |
114 <thead> | 144 // <thead> |
115 <tr> | 145 // <tr> |
116 <th></th> | 146 // <th></th> |
117 ${layout.preds.map(this._drawColumnHead.bind(this))} | 147 // ${layout.preds.map(this._drawColumnHead.bind(this))} |
118 </tr> | 148 // </tr> |
119 </thead> | 149 // </thead> |
120 `; | 150 // `; |
121 } | 151 // } |
122 | 152 |
123 _msbCell(layout: MultiSubjsTypeBlockLayout, subj: NamedNode) { | 153 // _msbCell(layout: MultiSubjsTypeBlockLayout, subj: NamedNode) { |
124 return (pred: NamedNode): TemplateResult => { | 154 // return (pred: NamedNode): TemplateResult => { |
125 const objs = layout.graphCells.get(layout.makeCellKey(subj, pred)); | 155 // const objs = layout.graphCells.get(layout.makeCellKey(subj, pred)); |
126 if (!objs || !objs.size) { | 156 // if (!objs || !objs.size) { |
127 return html` <td></td> `; | 157 // return html` <td></td> `; |
128 } | 158 // } |
129 const objsList = Array.from(objs); | 159 // const objsList = Array.from(objs); |
130 objsList.sort(); | 160 // objsList.sort(); |
131 return html` <td>${objsList.map(this._drawObj.bind(this))}</td> `; | 161 // return html` <td>${objsList.map(this._drawObj.bind(this))}</td> `; |
132 }; | 162 // }; |
133 } | 163 // } |
134 | 164 |
135 _instanceRow(layout: MultiSubjsTypeBlockLayout) { | 165 // _instanceRow(layout: MultiSubjsTypeBlockLayout) { |
136 return (subj: NamedNode): TemplateResult => { | 166 // return (subj: NamedNode): TemplateResult => { |
137 return html` | 167 // return html` |
138 <tr> | 168 // <tr> |
139 <td>${this.nodeDisplay.render(subj)}</td> | 169 // <td>${this.nodeDisplay.render(subj)}</td> |
140 ${layout.preds.map(this._msbCell(layout, subj))} | 170 // ${layout.preds.map(this._msbCell(layout, subj))} |
141 </tr> | 171 // </tr> |
142 `; | 172 // `; |
143 }; | 173 // }; |
144 } | 174 // } |
145 | 175 |
146 _multiSubjsTypeBlock(byType: TypeToSubjs, table: TableDesc) { | 176 // _multiSubjsTypeBlock(byType: TypeToSubjs, table: TableDesc) { |
147 const layout = new MultiSubjsTypeBlockLayout(this.graph, byType, table); | 177 // const layout = new MultiSubjsTypeBlockLayout(this.graph, byType, table); |
148 | 178 |
149 let typeNames = [html`${this.nodeDisplay.render(table.primary)}`]; | 179 // let typeNames = [html`${this.nodeDisplay.render(table.primary)}`]; |
150 if (table.joins) { | 180 // if (table.joins) { |
151 typeNames.push(html` joined with [`); | 181 // typeNames.push(html` joined with [`); |
152 for (let j of table.joins) { | 182 // for (let j of table.joins) { |
153 typeNames.push(html`${this.nodeDisplay.render(j)}`); | 183 // typeNames.push(html`${this.nodeDisplay.render(j)}`); |
154 } | 184 // } |
155 typeNames.push(html`]`); | 185 // typeNames.push(html`]`); |
156 } | 186 // } |
157 | 187 |
158 return html` | 188 // return html` |
159 <div>[icon] Resources of type ${typeNames}</div> | 189 // <div>[icon] Resources of type ${typeNames}</div> |
160 <div class="typeBlockScroll"> | 190 // <div class="typeBlockScroll"> |
161 <table class="typeBlock"> | 191 // <table class="typeBlock"> |
162 ${this._thead(layout)} | 192 // ${this._thead(layout)} ${layout.subjs.map(this._instanceRow(layout))} |
163 ${layout.subjs.map(this._instanceRow(layout))} | 193 // </table> |
164 </table> | 194 // </div> |
165 </div> | 195 // `; |
166 `; | 196 // } |
167 } | |
168 | |
169 async makeTemplate(): Promise<TemplateResult> { | |
170 await this.view.ready; | |
171 const { byType, typesPresent, untypedSubjs } = groupByRdfType(this.graph); | |
172 let viewTitle = html` (no view)`; | |
173 if (this.view.url) { | |
174 viewTitle = html` using view | |
175 <a href="${this.view.url}">${this.view.label()}</a>`; | |
176 } | |
177 const tables = this.view.toplevelTables(typesPresent); | |
178 return html` | |
179 <section> | |
180 <h2> | |
181 Current graph (<a href="${this.url}">${this.url}</a>)${viewTitle} | |
182 </h2> | |
183 <div> | |
184 <!-- todo: graphs and provenance. | |
185 These statements are all in the | |
186 <span data-bind="html: $root.createCurie(graphUri())">...</span> graph.--> | |
187 </div> | |
188 ${tables.map((t: TableDesc) => this._multiSubjsTypeBlock(byType, t))} | |
189 <div class="spoGrid"> | |
190 ${untypedSubjs.map(this._subjPredObjsBlock.bind(this))} | |
191 </div> | |
192 </section> | |
193 `; | |
194 } | |
195 } | 197 } |