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 }