Mercurial > code > home > repos > streamed-graph
comparison src/graph_view.ts @ 49:c16a331f42e5
rewrap; stylesheet
author | drewp@bigasterisk.com |
---|---|
date | Thu, 09 Jan 2020 00:05:32 -0800 |
parents | 648bd89f9d47 |
children | 490f569bb0c9 |
comparison
equal
deleted
inserted
replaced
48:b8e5850acca0 | 49:c16a331f42e5 |
---|---|
1 import { html, TemplateResult } from 'lit-html'; | 1 import { html, TemplateResult } from "lit-html"; |
2 import { Quad, Term, N3Store } from 'n3'; | 2 import { Quad, Term, N3Store } from "n3"; |
3 import { DataFactory, Util } from 'n3'; | 3 import { DataFactory, Util } from "n3"; |
4 const { namedNode } = DataFactory; | 4 const { namedNode } = DataFactory; |
5 import * as RDF from "rdf-js"; | 5 import * as RDF from "rdf-js"; |
6 type NamedNode = RDF.NamedNode; | 6 type NamedNode = RDF.NamedNode; |
7 | 7 |
8 import { SuffixLabels } from './suffixLabels'; | 8 import { SuffixLabels } from "./suffixLabels"; |
9 // import ns from 'n3/src/IRIs'; | 9 // import ns from 'n3/src/IRIs'; |
10 // const { rdf } = ns; | 10 // const { rdf } = ns; |
11 const rdf = { type: namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")}; | 11 const rdf = { |
12 type: namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type") | |
13 }; | |
12 | 14 |
13 type TypeToSubjs = Map<NamedNode, Set<NamedNode>>; | 15 type TypeToSubjs = Map<NamedNode, Set<NamedNode>>; |
14 function groupByRdfType(graph: N3Store): { byType: TypeToSubjs, untyped: Set<NamedNode> } { | 16 function groupByRdfType( |
17 graph: N3Store | |
18 ): { byType: TypeToSubjs; untyped: Set<NamedNode> } { | |
15 const rdfType = rdf.type; | 19 const rdfType = rdf.type; |
16 const byType: TypeToSubjs = new Map(); | 20 const byType: TypeToSubjs = new Map(); |
17 const untyped: Set<NamedNode> = new Set(); // subjs | 21 const untyped: Set<NamedNode> = new Set(); // subjs |
18 const internSubjs = new Map<string, NamedNode>(); | 22 const internSubjs = new Map<string, NamedNode>(); |
19 graph.forEach((q) => { | 23 graph.forEach( |
20 if (!Util.isNamedNode(q.subject)) { | 24 q => { |
21 throw new Error("unsupported " + q.subject.value); | 25 if (!Util.isNamedNode(q.subject)) { |
22 } | 26 throw new Error("unsupported " + q.subject.value); |
23 const subj = q.subject as NamedNode; | 27 } |
24 | 28 const subj = q.subject as NamedNode; |
25 let subjType: NamedNode | null = null; | 29 |
26 | 30 let subjType: NamedNode | null = null; |
27 graph.forObjects((o: Quad) => { | 31 |
28 if (Util.isNamedNode(o.object)) { | 32 graph.forObjects( |
29 subjType = o.object as NamedNode; | 33 (o: Quad) => { |
30 } | 34 if (Util.isNamedNode(o.object)) { |
31 }, subj, rdfType, null); | 35 subjType = o.object as NamedNode; |
32 | 36 } |
33 if (subjType !== null) { | 37 }, |
34 // (subj, rdf:type, subjType) in graph | 38 subj, |
35 if (!byType.has(subjType)) { | 39 rdfType, |
36 byType.set(subjType, new Set()); | 40 null |
37 } | 41 ); |
38 (byType.get(subjType) as Set<NamedNode>).add(subj); | 42 |
39 } else { | 43 if (subjType !== null) { |
40 // no rdf:type stmt in graph | 44 // (subj, rdf:type, subjType) in graph |
41 if (!internSubjs.has(subj.value)) { | 45 if (!byType.has(subjType)) { |
42 internSubjs.set(subj.value, subj); | 46 byType.set(subjType, new Set()); |
43 } | 47 } |
44 const intSubj: NamedNode = internSubjs.get(subj.value as string) as NamedNode; | 48 (byType.get(subjType) as Set<NamedNode>).add(subj); |
45 untyped.add(intSubj); | 49 } else { |
46 } | 50 // no rdf:type stmt in graph |
47 }, null, null, null, null); | 51 if (!internSubjs.has(subj.value)) { |
52 internSubjs.set(subj.value, subj); | |
53 } | |
54 const intSubj: NamedNode = internSubjs.get( | |
55 subj.value as string | |
56 ) as NamedNode; | |
57 untyped.add(intSubj); | |
58 } | |
59 }, | |
60 null, | |
61 null, | |
62 null, | |
63 null | |
64 ); | |
48 return { byType: byType, untyped: untyped }; | 65 return { byType: byType, untyped: untyped }; |
49 } | 66 } |
50 | |
51 | 67 |
52 class NodeDisplay { | 68 class NodeDisplay { |
53 labels: SuffixLabels; | 69 labels: SuffixLabels; |
54 constructor(labels: SuffixLabels) { | 70 constructor(labels: SuffixLabels) { |
55 this.labels = labels; | 71 this.labels = labels; |
56 } | 72 } |
57 getHtml(n: Term|NamedNode): TemplateResult { | 73 getHtml(n: Term | NamedNode): TemplateResult { |
58 if (n.termType == "Literal") { | 74 if (n.termType == "Literal") { |
59 let dtPart: any = ""; | 75 let dtPart: any = ""; |
60 if (n.datatype) { | 76 if (n.datatype) { |
61 dtPart = html` | 77 dtPart = html` |
62 ^^<span class="literalType"> | 78 ^^<span class="literalType"> |
63 ${this.getHtml(n.datatype)} | 79 ${this.getHtml(n.datatype)} |
64 </span>`; | 80 </span> |
65 } | 81 `; |
66 return html`<span class="literal">${n.value}${dtPart}</span>`; | 82 } |
83 return html` | |
84 <span class="literal">${n.value}${dtPart}</span> | |
85 `; | |
67 } | 86 } |
68 | 87 |
69 if (n.termType == "NamedNode") { | 88 if (n.termType == "NamedNode") { |
70 let shortened = false; | 89 let shortened = false; |
71 let uriValue: string = n.value; | 90 let uriValue: string = n.value; |
72 for (let [long, short] of [ | 91 for (let [long, short] of [ |
73 ["http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:"], | 92 ["http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:"], |
74 ["http://www.w3.org/2000/01/rdf-schema#", "rdfs:"], | 93 ["http://www.w3.org/2000/01/rdf-schema#", "rdfs:"], |
75 ["http://purl.org/dc/elements/1.1/", "dc:"], | 94 ["http://purl.org/dc/elements/1.1/", "dc:"], |
76 ["http://www.w3.org/2001/XMLSchema#", "xsd:"]]) { | 95 ["http://www.w3.org/2001/XMLSchema#", "xsd:"] |
96 ]) { | |
77 if (uriValue?.startsWith(long)) { | 97 if (uriValue?.startsWith(long)) { |
78 uriValue = short + uriValue.substr(long.length); | 98 uriValue = short + uriValue.substr(long.length); |
79 shortened = true; | 99 shortened = true; |
80 break; | 100 break; |
81 } | 101 } |
82 } | 102 } |
83 if (!shortened) { | 103 if (!shortened) { |
84 | |
85 let dn: string | undefined = this.labels.getLabelForNode(uriValue); | 104 let dn: string | undefined = this.labels.getLabelForNode(uriValue); |
86 if (dn === undefined) { | 105 if (dn === undefined) { |
87 throw new Error(`dn=${dn}`); | 106 throw new Error(`dn=${dn}`); |
88 } | 107 } |
89 uriValue = dn; | 108 uriValue = dn; |
90 } | 109 } |
91 | 110 |
92 | 111 return html` |
93 return html`<a class="graphUri" href="${n.value}">${uriValue}</a>`; | 112 <a class="graphUri" href="${n.value}">${uriValue}</a> |
113 `; | |
94 } | 114 } |
95 | 115 |
96 return html`[${n.termType} ${n.value}]`; | 116 return html` |
117 [${n.termType} ${n.value}] | |
118 `; | |
97 } | 119 } |
98 } | 120 } |
99 | 121 |
100 export class GraphView { | 122 export class GraphView { |
101 url: string; | 123 url: string; |
109 this._addLabelsForAllTerms(labels); | 131 this._addLabelsForAllTerms(labels); |
110 this.nodeDisplay = new NodeDisplay(labels); | 132 this.nodeDisplay = new NodeDisplay(labels); |
111 } | 133 } |
112 | 134 |
113 _addLabelsForAllTerms(labels: SuffixLabels) { | 135 _addLabelsForAllTerms(labels: SuffixLabels) { |
114 return this.graph.forEach((q: Quad) => { | 136 return this.graph.forEach( |
115 if (q.subject.termType === "NamedNode") { labels.planDisplayForNode(q.subject); } | 137 (q: Quad) => { |
116 if (q.predicate.termType === "NamedNode") { labels.planDisplayForNode(q.predicate); } | 138 if (q.subject.termType === "NamedNode") { |
117 if (q.object.termType === "NamedNode") { labels.planDisplayForNode(q.object); } | 139 labels.planDisplayForNode(q.subject); |
118 if (q.object.termType === "Literal" && q.object.datatype) { | 140 } |
119 labels.planDisplayForNode(q.object.datatype); | 141 if (q.predicate.termType === "NamedNode") { |
120 } | 142 labels.planDisplayForNode(q.predicate); |
121 }, null, null, null, null); | 143 } |
144 if (q.object.termType === "NamedNode") { | |
145 labels.planDisplayForNode(q.object); | |
146 } | |
147 if (q.object.termType === "Literal" && q.object.datatype) { | |
148 labels.planDisplayForNode(q.object.datatype); | |
149 } | |
150 }, | |
151 null, | |
152 null, | |
153 null, | |
154 null | |
155 ); | |
122 } | 156 } |
123 | 157 |
124 _subjBlock(subj: NamedNode) { | 158 _subjBlock(subj: NamedNode) { |
125 const predsSet: Set<NamedNode> = new Set(); | 159 const predsSet: Set<NamedNode> = new Set(); |
126 this.graph.forEach((q: Quad) => { | 160 this.graph.forEach( |
127 predsSet.add(q.predicate as NamedNode); | 161 (q: Quad) => { |
128 }, subj, null, null, null); | 162 predsSet.add(q.predicate as NamedNode); |
163 }, | |
164 subj, | |
165 null, | |
166 null, | |
167 null | |
168 ); | |
129 const preds = Array.from(predsSet.values()); | 169 const preds = Array.from(predsSet.values()); |
130 preds.sort(); | 170 preds.sort(); |
131 return html` | 171 return html` |
132 <div class="subject">${this.nodeDisplay.getHtml(subj)} | 172 <div class="subject"> |
173 ${this.nodeDisplay.getHtml(subj)} | |
133 <!-- todo: special section for uri/type-and-icon/label/comment --> | 174 <!-- todo: special section for uri/type-and-icon/label/comment --> |
134 <div> | 175 <div> |
135 ${preds.map((p) => { return this._predBlock(subj, p); })} | 176 ${preds.map(p => { |
177 return this._predBlock(subj, p); | |
178 })} | |
136 </div> | 179 </div> |
137 </div> | 180 </div> |
138 `; | 181 `; |
139 } | 182 } |
140 | 183 |
141 _objBlock(obj: Term) { | 184 _objBlock(obj: Term) { |
142 return html` | 185 return html` |
143 <div class="object"> | 186 <div class="object"> |
144 ${this.nodeDisplay.getHtml(obj)} <!-- indicate what source or graph said this stmt --> | 187 ${this.nodeDisplay.getHtml(obj)} |
188 <!-- indicate what source or graph said this stmt --> | |
145 </div> | 189 </div> |
146 `; | 190 `; |
147 } | 191 } |
148 | 192 |
149 _predBlock(subj: NamedNode, pred: NamedNode) { | 193 _predBlock(subj: NamedNode, pred: NamedNode) { |
150 const objsSet = new Set<Term>(); | 194 const objsSet = new Set<Term>(); |
151 this.graph.forEach((q: Quad) => { | 195 this.graph.forEach( |
152 objsSet.add(q.object); | 196 (q: Quad) => { |
153 }, subj, pred, null, null); | 197 objsSet.add(q.object); |
198 }, | |
199 subj, | |
200 pred, | |
201 null, | |
202 null | |
203 ); | |
154 const objs = Array.from(objsSet.values()); | 204 const objs = Array.from(objsSet.values()); |
155 objs.sort(); | 205 objs.sort(); |
156 return html` | 206 return html` |
157 <div class="predicate">${this.nodeDisplay.getHtml(pred)} | 207 <div class="predicate"> |
208 ${this.nodeDisplay.getHtml(pred)} | |
158 <div> | 209 <div> |
159 ${objs.map(this._objBlock.bind(this))} | 210 ${objs.map(this._objBlock.bind(this))} |
160 </div> | 211 </div> |
161 </div> | 212 </div> |
162 `; | 213 `; |
163 } | 214 } |
164 | 215 |
165 | |
166 byTypeBlock(byType: TypeToSubjs, typeUri: NamedNode) { | 216 byTypeBlock(byType: TypeToSubjs, typeUri: NamedNode) { |
167 const subjSet = byType.get(typeUri); | 217 const subjSet = byType.get(typeUri); |
168 const subjs: Array<NamedNode> = subjSet ? Array.from(subjSet) : []; | 218 const subjs: Array<NamedNode> = subjSet ? Array.from(subjSet) : []; |
169 subjs.sort(); | 219 subjs.sort(); |
170 | 220 |
171 const graphCells = new Map<string, Set<Term>>(); // [subj, pred] : objs | 221 const graphCells = new Map<string, Set<Term>>(); // [subj, pred] : objs |
172 const makeCellKey = (subj: NamedNode, pred: NamedNode) => subj.value + '|||' + pred.value; | 222 const makeCellKey = (subj: NamedNode, pred: NamedNode) => |
173 const preds = new Set<NamedNode>(); | 223 subj.value + "|||" + pred.value; |
174 | 224 const preds = new Set<NamedNode>(); |
175 subjs.forEach((subj: NamedNode) => { | 225 |
176 this.graph.forEach((q: Quad) => { | 226 subjs.forEach((subj: NamedNode) => { |
227 this.graph.forEach( | |
228 (q: Quad) => { | |
177 if (!Util.isNamedNode(q.predicate)) { | 229 if (!Util.isNamedNode(q.predicate)) { |
178 throw new Error(); | 230 throw new Error(); |
179 } | 231 } |
180 preds.add(q.predicate as NamedNode); | 232 preds.add(q.predicate as NamedNode); |
181 const cellKey = makeCellKey(subj, q.predicate as NamedNode); | 233 const cellKey = makeCellKey(subj, q.predicate as NamedNode); |
182 if (!graphCells.has(cellKey)) { | 234 if (!graphCells.has(cellKey)) { |
183 graphCells.set(cellKey, new Set<Term>()); | 235 graphCells.set(cellKey, new Set<Term>()); |
184 } | 236 } |
185 graphCells.get(cellKey)!.add(q.object); | 237 graphCells.get(cellKey)!.add(q.object); |
186 }, subj, null, null, null); | 238 }, |
187 }); | 239 subj, |
188 const predsList = Array.from(preds); | 240 null, |
189 predsList.splice(predsList.indexOf(rdf.type), 1); | 241 null, |
190 // also pull out label, which should be used on 1st column | 242 null |
191 predsList.sort(); | 243 ); |
192 | 244 }); |
193 const thead = () => { | 245 const predsList = Array.from(preds); |
194 const predColumnHead = (pred: NamedNode) => { | 246 predsList.splice(predsList.indexOf(rdf.type), 1); |
195 return html`<th>${this.nodeDisplay.getHtml(pred)}</th>`; | 247 // also pull out label, which should be used on 1st column |
248 predsList.sort(); | |
249 | |
250 const thead = () => { | |
251 const predColumnHead = (pred: NamedNode) => { | |
252 return html` | |
253 <th>${this.nodeDisplay.getHtml(pred)}</th> | |
254 `; | |
255 }; | |
256 return html` | |
257 <thead> | |
258 <tr> | |
259 <th></th> | |
260 ${predsList.map(predColumnHead)} | |
261 </tr> | |
262 </thead> | |
263 `; | |
264 }; | |
265 | |
266 const instanceRow = (subj: NamedNode) => { | |
267 const cell = (pred: NamedNode) => { | |
268 const objs = graphCells.get(subj + "|||" + pred); | |
269 if (!objs) { | |
270 return html` | |
271 <td></td> | |
272 `; | |
273 } | |
274 const objsList = Array.from(objs); | |
275 objsList.sort(); | |
276 const draw = (obj: Term) => { | |
277 return html` | |
278 <div>${this.nodeDisplay.getHtml(obj)}</div> | |
279 `; | |
196 }; | 280 }; |
197 return html` | 281 return html` |
198 <thead> | 282 <td>${objsList.map(draw)}</td> |
199 <tr> | 283 `; |
200 <th></th> | |
201 ${predsList.map(predColumnHead)} | |
202 </tr> | |
203 </thead>`; | |
204 }; | 284 }; |
205 | 285 |
206 const instanceRow = (subj: NamedNode) => { | |
207 const cell = (pred: NamedNode) => { | |
208 const objs = graphCells.get(subj + '|||' + pred); | |
209 if (!objs) { | |
210 return html`<td></td>`; | |
211 } | |
212 const objsList = Array.from(objs); | |
213 objsList.sort(); | |
214 const draw = (obj: Term) => { | |
215 return html`<div>${this.nodeDisplay.getHtml(obj)}</div>` | |
216 }; | |
217 return html`<td>${objsList.map(draw)}</td>`; | |
218 }; | |
219 | |
220 return html` | |
221 <tr> | |
222 <td>${this.nodeDisplay.getHtml(subj)}</td> | |
223 ${predsList.map(cell)} | |
224 </tr> | |
225 `; | |
226 }; | |
227 | |
228 return html` | 286 return html` |
229 <div>[icon] ${this.nodeDisplay.getHtml(typeUri)} resources</div> | 287 <tr> |
230 <div class="typeBlockScroll"> | 288 <td>${this.nodeDisplay.getHtml(subj)}</td> |
231 <table class="typeBlock"> | 289 ${predsList.map(cell)} |
232 ${thead()} | 290 </tr> |
233 ${subjs.map(instanceRow)} | 291 `; |
234 </table> | |
235 </div> | |
236 `; | |
237 }; | 292 }; |
238 | 293 |
294 return html` | |
295 <div>[icon] ${this.nodeDisplay.getHtml(typeUri)} resources</div> | |
296 <div class="typeBlockScroll"> | |
297 <table class="typeBlock"> | |
298 ${thead()} ${subjs.map(instanceRow)} | |
299 </table> | |
300 </div> | |
301 `; | |
302 } | |
303 | |
239 makeTemplate(): TemplateResult { | 304 makeTemplate(): TemplateResult { |
240 | |
241 const { byType, untyped } = groupByRdfType(this.graph); | 305 const { byType, untyped } = groupByRdfType(this.graph); |
242 const typedSubjs = Array.from(byType.keys()); | 306 const typedSubjs = Array.from(byType.keys()); |
243 typedSubjs.sort(); | 307 typedSubjs.sort(); |
244 | 308 |
245 const untypedSubjs = Array.from(untyped.values()); | 309 const untypedSubjs = Array.from(untyped.values()); |
246 untypedSubjs.sort(); | 310 untypedSubjs.sort(); |
247 | 311 |
248 return html` | 312 return html` |
249 <link rel="stylesheet" href="../src/streamed-graph.css"> | 313 <section> |
250 | 314 <h2>Current graph (<a href="${this.url}">${this.url}</a>)</h2> |
251 <section> | 315 <div> |
252 <h2> | 316 <!-- todo: graphs and provenance. |
253 Current graph (<a href="${this.url}">${this.url}</a>) | |
254 </h2> | |
255 <div> | |
256 <!-- todo: graphs and provenance. | |
257 These statements are all in the | 317 These statements are all in the |
258 <span data-bind="html: $root.createCurie(graphUri())">...</span> graph.--> | 318 <span data-bind="html: $root.createCurie(graphUri())">...</span> graph.--> |
259 </div> | 319 </div> |
260 ${typedSubjs.map((t: NamedNode) => this.byTypeBlock(byType, t))} | 320 ${typedSubjs.map((t: NamedNode) => this.byTypeBlock(byType, t))} |
261 <div class="spoGrid"> | 321 <div class="spoGrid"> |
262 ${untypedSubjs.map(this._subjBlock.bind(this))} | 322 ${untypedSubjs.map(this._subjBlock.bind(this))} |
263 </div> | 323 </div> |
264 </section> | 324 </section> |
265 `; | 325 `; |
266 } | 326 } |
267 } | 327 } |