changeset 107:042bd3361339

renames
author drewp@bigasterisk.com
date Sun, 13 Mar 2022 22:02:30 -0700
parents 2468f2227d22
children 5e6840229a05
files src/index.ts src/layout/StreamedGraphClient.ts src/layout/streamed_graph_client.ts src/render/GraphView.ts src/render/StreamedGraph.ts src/render/element.ts src/render/graph_view.ts
diffstat 7 files changed, 488 insertions(+), 489 deletions(-) [+]
line wrap: on
line diff
--- a/src/index.ts	Sun Mar 13 22:00:30 2022 -0700
+++ b/src/index.ts	Sun Mar 13 22:02:30 2022 -0700
@@ -1,3 +1,3 @@
-import { StreamedGraph } from "./render/element";
+import { StreamedGraph } from "./render/StreamedGraph";
 
 export {StreamedGraph}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/layout/StreamedGraphClient.ts	Sun Mar 13 22:02:30 2022 -0700
@@ -0,0 +1,142 @@
+import { eachJsonLdQuad } from "./json_ld_quads";
+import { Store } from "n3";
+
+export class StreamedGraphClient {
+  // holds a n3 Store, which is synced to a server-side
+  // store that sends patches over SSE
+
+  onStatus: (msg: string) => void = function (m) {};
+  onGraphChanged: () => void = function () {};
+  store: Store;
+  _deletedCount: number = 0;
+  events!: EventSource;
+  constructor(
+    eventsUrl: string,
+    onGraphChanged: () => void,
+    onStatus: (status: string) => void,
+    prefixes: Array<Record<string, string>>,
+    staticGraphUrls: Array<string>
+  ) {
+    console.log("new StreamedGraph", eventsUrl);
+    this.onStatus = onStatus;
+    this.onGraphChanged = onGraphChanged;
+    this.onStatus("startup...");
+
+    this.store = new Store();
+
+    // Object.keys(prefixes).forEach((prefix) => {
+    //     this.store.setPrefix(prefix, prefixes[prefix]);
+    // });
+
+    this.connect(eventsUrl);
+    this.reconnectOnWake();
+
+    //     staticGraphUrls.forEach((url) => {
+    //         fetch(url).then((response) => response.text())
+    //             .then((body) => {
+    //                 // parse with n3, add to output
+    //             });
+    //     });
+  }
+
+  _vacuum() {
+    // workaround for the growing _ids map
+    this.store = new Store(this.store.getQuads(null, null, null, null));
+  }
+
+  reconnectOnWake() {
+    // it's not this, which fires on every mouse-in on a browser window, and doesn't seem to work for screen-turned-back-on
+    //window.addEventListener('focus', function() { this.connect(eventsUrl); }.bind(this));
+  }
+
+  connect(eventsUrl: string) {
+    // need to exit here if this obj has been replaced
+
+    this.onStatus("start connect...");
+    this.close();
+    if (this.events && this.events.readyState != EventSource.CLOSED) {
+      this.onStatus("zombie");
+      throw new Error("zombie eventsource");
+    }
+
+    this.events = new EventSource(eventsUrl, { withCredentials: true });
+
+    this.events.addEventListener("error", (ev) => {
+      // todo: this is piling up tons of retries and eventually multiple connections
+      this.testEventUrl(eventsUrl);
+      this.onStatus("connection lost- retrying");
+      setTimeout(() => {
+        requestAnimationFrame(() => {
+          this.connect(eventsUrl);
+        });
+      }, 3000);
+    });
+
+    this.events.addEventListener("fullGraph", async (ev) => {
+      this.onStatus("sync- full graph update");
+      await this.replaceFullGraph((ev as MessageEvent).data);
+      this.onStatus(`synced ${this.store.size}`);
+      this.onGraphChanged();
+    });
+
+    this.events.addEventListener("patch", async (ev) => {
+      this.onStatus("sync- updating");
+      await this.patchGraph((ev as MessageEvent).data);
+      window.setTimeout(() => {
+        this.onStatus(`synced ${this.store.size}`);
+      }, 60);
+      this.onGraphChanged();
+    });
+    this.onStatus("connecting...");
+  }
+
+  // these need some locks
+  async replaceFullGraph(jsonLdText: string) {
+    this.store = new Store();
+    await eachJsonLdQuad(
+      JSON.parse(jsonLdText),
+      this.store.addQuad.bind(this.store)
+    );
+  }
+
+  async patchGraph(patchJson: string) {
+    var patch = JSON.parse(patchJson).patch;
+
+    await eachJsonLdQuad(patch.deletes, (quad) => {
+      this.store.removeQuad(quad);
+      this._deletedCount++;
+    });
+    await eachJsonLdQuad(patch.adds, this.store.addQuad.bind(this.store));
+
+    if (this._deletedCount > 100) {
+      this._vacuum();
+      this._deletedCount = 0;
+    }
+  }
+
+  close() {
+    if (this.events) {
+      this.events.close();
+    }
+  }
+
+  async testEventUrl(eventsUrl: string): Promise<void> {
+    return new Promise<void>((resolve, reject) => {
+      this.onStatus("testing connection");
+      fetch(eventsUrl, {
+        method: "HEAD",
+        credentials: "include",
+      })
+        .then((value) => {
+          if (value.status == 403) {
+            reject();
+            return;
+          }
+          resolve();
+        })
+        .catch((err) => {
+          reject();
+        });
+    });
+  }
+}
--- a/src/layout/streamed_graph_client.ts	Sun Mar 13 22:00:30 2022 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,142 +0,0 @@
-import { eachJsonLdQuad } from "./json_ld_quads";
-import { Store } from "n3";
-
-export class StreamedGraphClient {
-  // holds a n3 Store, which is synced to a server-side
-  // store that sends patches over SSE
-
-  onStatus: (msg: string) => void = function (m) {};
-  onGraphChanged: () => void = function () {};
-  store: Store;
-  _deletedCount: number = 0;
-  events!: EventSource;
-  constructor(
-    eventsUrl: string,
-    onGraphChanged: () => void,
-    onStatus: (status: string) => void,
-    prefixes: Array<Record<string, string>>,
-    staticGraphUrls: Array<string>
-  ) {
-    console.log("new StreamedGraph", eventsUrl);
-    this.onStatus = onStatus;
-    this.onGraphChanged = onGraphChanged;
-    this.onStatus("startup...");
-
-    this.store = new Store();
-
-    // Object.keys(prefixes).forEach((prefix) => {
-    //     this.store.setPrefix(prefix, prefixes[prefix]);
-    // });
-
-    this.connect(eventsUrl);
-    this.reconnectOnWake();
-
-    //     staticGraphUrls.forEach((url) => {
-    //         fetch(url).then((response) => response.text())
-    //             .then((body) => {
-    //                 // parse with n3, add to output
-    //             });
-    //     });
-  }
-
-  _vacuum() {
-    // workaround for the growing _ids map
-    this.store = new Store(this.store.getQuads(null, null, null, null));
-  }
-
-  reconnectOnWake() {
-    // it's not this, which fires on every mouse-in on a browser window, and doesn't seem to work for screen-turned-back-on
-    //window.addEventListener('focus', function() { this.connect(eventsUrl); }.bind(this));
-  }
-
-  connect(eventsUrl: string) {
-    // need to exit here if this obj has been replaced
-
-    this.onStatus("start connect...");
-    this.close();
-    if (this.events && this.events.readyState != EventSource.CLOSED) {
-      this.onStatus("zombie");
-      throw new Error("zombie eventsource");
-    }
-
-    this.events = new EventSource(eventsUrl, { withCredentials: true });
-
-    this.events.addEventListener("error", (ev) => {
-      // todo: this is piling up tons of retries and eventually multiple connections
-      this.testEventUrl(eventsUrl);
-      this.onStatus("connection lost- retrying");
-      setTimeout(() => {
-        requestAnimationFrame(() => {
-          this.connect(eventsUrl);
-        });
-      }, 3000);
-    });
-
-    this.events.addEventListener("fullGraph", async (ev) => {
-      this.onStatus("sync- full graph update");
-      await this.replaceFullGraph((ev as MessageEvent).data);
-      this.onStatus(`synced ${this.store.size}`);
-      this.onGraphChanged();
-    });
-
-    this.events.addEventListener("patch", async (ev) => {
-      this.onStatus("sync- updating");
-      await this.patchGraph((ev as MessageEvent).data);
-      window.setTimeout(() => {
-        this.onStatus(`synced ${this.store.size}`);
-      }, 60);
-      this.onGraphChanged();
-    });
-    this.onStatus("connecting...");
-  }
-
-  // these need some locks
-  async replaceFullGraph(jsonLdText: string) {
-    this.store = new Store();
-    await eachJsonLdQuad(
-      JSON.parse(jsonLdText),
-      this.store.addQuad.bind(this.store)
-    );
-  }
-
-  async patchGraph(patchJson: string) {
-    var patch = JSON.parse(patchJson).patch;
-
-    await eachJsonLdQuad(patch.deletes, (quad) => {
-      this.store.removeQuad(quad);
-      this._deletedCount++;
-    });
-    await eachJsonLdQuad(patch.adds, this.store.addQuad.bind(this.store));
-
-    if (this._deletedCount > 100) {
-      this._vacuum();
-      this._deletedCount = 0;
-    }
-  }
-
-  close() {
-    if (this.events) {
-      this.events.close();
-    }
-  }
-
-  async testEventUrl(eventsUrl: string): Promise<void> {
-    return new Promise<void>((resolve, reject) => {
-      this.onStatus("testing connection");
-      fetch(eventsUrl, {
-        method: "HEAD",
-        credentials: "include",
-      })
-        .then((value) => {
-          if (value.status == 403) {
-            reject();
-            return;
-          }
-          resolve();
-        })
-        .catch((err) => {
-          reject();
-        });
-    });
-  }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/render/GraphView.ts	Sun Mar 13 22:02:30 2022 -0700
@@ -0,0 +1,195 @@
+import { html, TemplateResult } from "lit";
+import { DataFactory, Literal, NamedNode, Quad, Store, Term } from "n3";
+import { NodeDisplay } from "./NodeDisplay";
+import { SuffixLabels } from "../layout/suffixLabels";
+import { Layout } from "../layout/Layout";
+import { TableDesc, ViewConfig } from "../layout/ViewConfig";
+
+const { namedNode } = DataFactory;
+
+// https://github.com/rdfjs/N3.js/issues/265
+if ((Literal.prototype as any).hashCode === undefined) {
+  (Literal.prototype as any).hashCode = () => 0;
+}
+if ((NamedNode.prototype as any).hashCode === undefined) {
+  (NamedNode.prototype as any).hashCode = () => 0;
+}
+export class GraphView {
+  url: string;
+  view: View;
+  graph: Store;
+  nodeDisplay: NodeDisplay;
+  constructor(url: string, viewUrl: string, graph: Store) {
+    this.url = url;
+    this.view = new View(viewUrl);
+    this.graph = graph;
+
+    const labels = new SuffixLabels();
+    this._addLabelsForAllTerms(this.graph, labels);
+    
+    this.view.ready.then(() => {
+      this._addLabelsForAllTerms(this.view.graph, labels);
+    });
+    this.nodeDisplay = new NodeDisplay(labels);
+  }
+
+  _addLabelsForAllTerms(graph: Store, labels: SuffixLabels) {
+    graph.forEach(
+      (q: Quad) => {
+        if (q.subject.termType === "NamedNode") {
+          labels.planDisplayForNode(q.subject);
+        }
+        if (q.predicate.termType === "NamedNode") {
+          labels.planDisplayForNode(q.predicate);
+        }
+        if (q.object.termType === "NamedNode") {
+          labels.planDisplayForNode(q.object);
+        }
+        if (q.object.termType === "Literal" && q.object.datatype) {
+          labels.planDisplayForNode(q.object.datatype);
+        }
+      },
+      null,
+      null,
+      null,
+      null
+    );
+  }
+
+  _subjPredObjsBlock(subj: NamedNode) {
+    const columns = predsForSubj(this.graph, subj);
+    return html`
+      <div class="subject">
+        ${this.nodeDisplay.render(subj)}
+        <!-- todo: special section for uri/type-and-icon/label/comment -->
+        <div>
+          ${columns.map((p) => {
+            return this._predObjsBlock(subj, p);
+          })}
+        </div>
+      </div>
+    `;
+  }
+
+  _objCell(obj: Term) {
+    return html`
+      <div class="object">
+        ${this.nodeDisplay.render(obj)}
+        <!-- indicate what source or graph said this stmt -->
+      </div>
+    `;
+  }
+
+  _predObjsBlock(subj: NamedNode, pred: NamedNode) {
+    const objsSet = new Set<Term>();
+    this.graph.forEach(
+      (q: Quad) => {
+        objsSet.add(q.object);
+      },
+      subj,
+      pred,
+      null,
+      null
+    );
+    const objs = Array.from(objsSet.values());
+    objs.sort();
+    return html`
+      <div class="predicate">
+        ${this.nodeDisplay.render(pred)}
+        <div>${objs.map(this._objCell.bind(this))}</div>
+      </div>
+    `;
+  }
+
+  _drawObj(obj: Term): TemplateResult {
+    return html` <div>${this.nodeDisplay.render(obj)}</div> `;
+  }
+
+  _drawColumnHead(pred: NamedNode): TemplateResult {
+    return html` <th>${this.nodeDisplay.render(pred)}</th> `;
+  }
+
+  _thead(layout: MultiSubjsTypeBlockLayout): TemplateResult {
+    return html`
+      <thead>
+        <tr>
+          <th></th>
+          ${layout.preds.map(this._drawColumnHead.bind(this))}
+        </tr>
+      </thead>
+    `;
+  }
+
+  _msbCell(layout: MultiSubjsTypeBlockLayout, subj: NamedNode) {
+    return (pred: NamedNode): TemplateResult => {
+      const objs = layout.graphCells.get(layout.makeCellKey(subj, pred));
+      if (!objs || !objs.size) {
+        return html` <td></td> `;
+      }
+      const objsList = Array.from(objs);
+      objsList.sort();
+      return html` <td>${objsList.map(this._drawObj.bind(this))}</td> `;
+    };
+  }
+
+  _instanceRow(layout: MultiSubjsTypeBlockLayout) {
+    return (subj: NamedNode): TemplateResult => {
+      return html`
+        <tr>
+          <td>${this.nodeDisplay.render(subj)}</td>
+          ${layout.preds.map(this._msbCell(layout, subj))}
+        </tr>
+      `;
+    };
+  }
+
+  _multiSubjsTypeBlock(byType: TypeToSubjs, table: TableDesc) {
+    const layout = new MultiSubjsTypeBlockLayout(this.graph, byType, table);
+
+    let typeNames = [html`${this.nodeDisplay.render(table.primary)}`];
+    if (table.joins) {
+      typeNames.push(html` joined with [`);
+      for (let j of table.joins) {
+        typeNames.push(html`${this.nodeDisplay.render(j)}`);
+      }
+      typeNames.push(html`]`);
+    }
+
+    return html`
+      <div>[icon] Resources of type ${typeNames}</div>
+      <div class="typeBlockScroll">
+        <table class="typeBlock">
+          ${this._thead(layout)} 
+          ${layout.subjs.map(this._instanceRow(layout))}
+        </table>
+      </div>
+    `;
+  }
+
+  async makeTemplate(): Promise<TemplateResult> {
+    await this.view.ready;
+    const { byType, typesPresent, untypedSubjs } = groupByRdfType(this.graph);
+    let viewTitle = html` (no view)`;
+    if (this.view.url) {
+      viewTitle = html` using view
+        <a href="${this.view.url}">${this.view.label()}</a>`;
+    }
+    const tables = this.view.toplevelTables(typesPresent);
+    return html`
+      <section>
+        <h2>
+          Current graph (<a href="${this.url}">${this.url}</a>)${viewTitle}
+        </h2>
+        <div>
+          <!-- todo: graphs and provenance.
+            These statements are all in the
+            <span data-bind="html: $root.createCurie(graphUri())">...</span> graph.-->
+        </div>
+        ${tables.map((t: TableDesc) => this._multiSubjsTypeBlock(byType, t))}
+        <div class="spoGrid">
+          ${untypedSubjs.map(this._subjPredObjsBlock.bind(this))}
+        </div>
+      </section>
+    `;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/render/StreamedGraph.ts	Sun Mar 13 22:02:30 2022 -0700
@@ -0,0 +1,150 @@
+import { LitElement, html, render, TemplateResult } from "lit";
+import { customElement, property } from "lit/decorators.js";
+
+import { Store } from "n3";
+
+import { GraphView } from "./GraphView";
+import { StreamedGraphClient } from "../layout/StreamedGraphClient";
+import { style, addFontToRootPage } from "./style";
+
+// export * from "./graph_queries";
+
+export interface VersionedGraph {
+  version: number;
+  store: Store;
+}
+
+@customElement("streamed-graph")
+export class StreamedGraph extends LitElement {
+  @property({ type: String })
+  url: string = "";
+  @property({ type: String })
+  view: string = "";
+  @property({ type: Object })
+  graph!: VersionedGraph;
+
+  @property({ type: Boolean })
+  expanded: boolean = false;
+
+  @property({ type: String })
+  status: string = "";
+
+  sg!: StreamedGraphClient;
+  graphViewDirty = true;
+
+  static styles = style;
+
+  render() {
+    const expandAction = this.expanded ? "-" : "+";
+    return html`
+      <div id="ui">
+        <span class="expander"
+          ><button @click="${this.toggleExpand}">${expandAction}</button></span
+        >
+        StreamedGraph <a href="${this.url}">[source]</a>: ${this.status}
+      </div>
+      <div id="graphView"></div>
+    `;
+  }
+
+  connectedCallback() {
+    super.connectedCallback();
+    addFontToRootPage();
+    const emptyStore = new Store();
+    this.graph = { version: -1, store: emptyStore };
+
+    this._onUrl(this.url); // todo: watch for changes and rebuild
+    if (this.expanded) {
+      this.redrawGraph();
+    }
+  }
+
+  toggleExpand() {
+    this.expanded = !this.expanded;
+    if (this.expanded) {
+      this.redrawGraph();
+    } else {
+      this.graphViewDirty = false;
+      this._graphAreaClose();
+    }
+  }
+
+  redrawGraph() {
+    this.graphViewDirty = true;
+    const rl: ()=>Promise<void> = this._redrawLater.bind(this)
+    requestAnimationFrame(rl);
+  }
+
+  _onUrl(url: string) {
+    if (this.sg) {
+      this.sg.close();
+    }
+    this.sg = new StreamedGraphClient(
+      url,
+      this.onGraphChanged.bind(this),
+      (st) => {
+        this.status = st;
+      },
+      [], //window.NS,
+      []
+    );
+  }
+
+  onGraphChanged() {
+    this.graph = {
+      version: this.graph.version + 1,
+      store: this.sg.store,
+    };
+    if (this.expanded) {
+      this.redrawGraph();
+    }
+    this.dispatchEvent(
+      new CustomEvent("graph-changed", { detail: { graph: this.graph } })
+    );
+  }
+
+  async _redrawLater() {
+    if (!this.graphViewDirty) return;
+
+    if ((this.graph as VersionedGraph).store && this.graph.store) {
+      await this._graphAreaShowGraph(
+        new GraphView(this.url, this.view, this.graph.store)
+      );
+      this.graphViewDirty = false;
+    } else {
+      this._graphAreaShowPending();
+    }
+  }
+
+  _graphAreaClose() {
+    this._setGraphArea(html``);
+  }
+
+  _graphAreaShowPending() {
+    this._setGraphArea(html` <span>waiting for data...</span> `);
+  }
+
+  async _graphAreaShowGraph(graphView: GraphView) {
+    this._setGraphArea(await graphView.makeTemplate());
+  }
+
+  _setGraphArea(tmpl: TemplateResult) {
+    const el = this.shadowRoot?.getElementById("graphView");
+    if (!el) {
+      return;
+    }
+    render(tmpl, el);
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    "streamed-graph": StreamedGraph;
+  }
+}
+
+// // allow child nodes to combine a few graphs and statics
+// //<streamed-graph id="timebankGraph"  graph="{{graph}}" expanded="true">
+// //  <member-graph url="graph/timebank/events"></member-graph>
+// //  <member-graph url="/some/static.n3"></member-graph>
+// //</streamed-graph>
--- a/src/render/element.ts	Sun Mar 13 22:00:30 2022 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,150 +0,0 @@
-import { LitElement, html, render, TemplateResult } from "lit";
-import { customElement, property } from "lit/decorators.js";
-
-import { Store } from "n3";
-
-import { GraphView } from "./graph_view";
-import { StreamedGraphClient } from "../layout/streamed_graph_client";
-import { style, addFontToRootPage } from "./style";
-
-// export * from "./graph_queries";
-
-export interface VersionedGraph {
-  version: number;
-  store: Store;
-}
-
-@customElement("streamed-graph")
-export class StreamedGraph extends LitElement {
-  @property({ type: String })
-  url: string = "";
-  @property({ type: String })
-  view: string = "";
-  @property({ type: Object })
-  graph!: VersionedGraph;
-
-  @property({ type: Boolean })
-  expanded: boolean = false;
-
-  @property({ type: String })
-  status: string = "";
-
-  sg!: StreamedGraphClient;
-  graphViewDirty = true;
-
-  static styles = style;
-
-  render() {
-    const expandAction = this.expanded ? "-" : "+";
-    return html`
-      <div id="ui">
-        <span class="expander"
-          ><button @click="${this.toggleExpand}">${expandAction}</button></span
-        >
-        StreamedGraph <a href="${this.url}">[source]</a>: ${this.status}
-      </div>
-      <div id="graphView"></div>
-    `;
-  }
-
-  connectedCallback() {
-    super.connectedCallback();
-    addFontToRootPage();
-    const emptyStore = new Store();
-    this.graph = { version: -1, store: emptyStore };
-
-    this._onUrl(this.url); // todo: watch for changes and rebuild
-    if (this.expanded) {
-      this.redrawGraph();
-    }
-  }
-
-  toggleExpand() {
-    this.expanded = !this.expanded;
-    if (this.expanded) {
-      this.redrawGraph();
-    } else {
-      this.graphViewDirty = false;
-      this._graphAreaClose();
-    }
-  }
-
-  redrawGraph() {
-    this.graphViewDirty = true;
-    const rl: ()=>Promise<void> = this._redrawLater.bind(this)
-    requestAnimationFrame(rl);
-  }
-
-  _onUrl(url: string) {
-    if (this.sg) {
-      this.sg.close();
-    }
-    this.sg = new StreamedGraphClient(
-      url,
-      this.onGraphChanged.bind(this),
-      (st) => {
-        this.status = st;
-      },
-      [], //window.NS,
-      []
-    );
-  }
-
-  onGraphChanged() {
-    this.graph = {
-      version: this.graph.version + 1,
-      store: this.sg.store,
-    };
-    if (this.expanded) {
-      this.redrawGraph();
-    }
-    this.dispatchEvent(
-      new CustomEvent("graph-changed", { detail: { graph: this.graph } })
-    );
-  }
-
-  async _redrawLater() {
-    if (!this.graphViewDirty) return;
-
-    if ((this.graph as VersionedGraph).store && this.graph.store) {
-      await this._graphAreaShowGraph(
-        new GraphView(this.url, this.view, this.graph.store)
-      );
-      this.graphViewDirty = false;
-    } else {
-      this._graphAreaShowPending();
-    }
-  }
-
-  _graphAreaClose() {
-    this._setGraphArea(html``);
-  }
-
-  _graphAreaShowPending() {
-    this._setGraphArea(html` <span>waiting for data...</span> `);
-  }
-
-  async _graphAreaShowGraph(graphView: GraphView) {
-    this._setGraphArea(await graphView.makeTemplate());
-  }
-
-  _setGraphArea(tmpl: TemplateResult) {
-    const el = this.shadowRoot?.getElementById("graphView");
-    if (!el) {
-      return;
-    }
-    render(tmpl, el);
-  }
-}
-
-declare global {
-  interface HTMLElementTagNameMap {
-    "streamed-graph": StreamedGraph;
-  }
-}
-
-// // allow child nodes to combine a few graphs and statics
-// //<streamed-graph id="timebankGraph"  graph="{{graph}}" expanded="true">
-// //  <member-graph url="graph/timebank/events"></member-graph>
-// //  <member-graph url="/some/static.n3"></member-graph>
-// //</streamed-graph>
--- a/src/render/graph_view.ts	Sun Mar 13 22:00:30 2022 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,196 +0,0 @@
-import { html, TemplateResult } from "lit";
-import { DataFactory, Literal, NamedNode, Quad, Store, Term } from "n3";
-import { NodeDisplay } from "./NodeDisplay";
-import { SuffixLabels } from "../layout/suffixLabels";
-import { Layout } from "../layout/Layout";
-import { TableDesc, ViewConfig } from "../layout/ViewConfig";
-
-const { namedNode } = DataFactory;
-
-// https://github.com/rdfjs/N3.js/issues/265
-if ((Literal.prototype as any).hashCode === undefined) {
-  (Literal.prototype as any).hashCode = () => 0;
-}
-if ((NamedNode.prototype as any).hashCode === undefined) {
-  (NamedNode.prototype as any).hashCode = () => 0;
-}
-export class GraphView {
-  url: string;
-  view: View;
-  graph: Store;
-  nodeDisplay: NodeDisplay;
-  constructor(url: string, viewUrl: string, graph: Store) {
-    this.url = url;
-    this.view = new View(viewUrl);
-    this.graph = graph;
-
-    const labels = new SuffixLabels();
-    this._addLabelsForAllTerms(this.graph, labels);
-    
-    if (this.view.graph) {
-      this._addLabelsForAllTerms(this.view.graph, labels);
-    }
-    this.nodeDisplay = new NodeDisplay(labels);
-  }
-
-  _addLabelsForAllTerms(graph: Store, labels: SuffixLabels) {
-    console.log("_addLabelsForAllTerms");
-
-    graph.forEach(
-      (q: Quad) => {
-        if (q.subject.termType === "NamedNode") {
-          labels.planDisplayForNode(q.subject);
-        }
-        if (q.predicate.termType === "NamedNode") {
-          labels.planDisplayForNode(q.predicate);
-        }
-        if (q.object.termType === "NamedNode") {
-          labels.planDisplayForNode(q.object);
-        }
-        if (q.object.termType === "Literal" && q.object.datatype) {
-          labels.planDisplayForNode(q.object.datatype);
-        }
-      },
-      null,
-      null,
-      null,
-      null
-    );
-  }
-
-  _subjPredObjsBlock(subj: NamedNode) {
-    const columns = predsForSubj(this.graph, subj);
-    return html`
-      <div class="subject">
-        ${this.nodeDisplay.render(subj)}
-        <!-- todo: special section for uri/type-and-icon/label/comment -->
-        <div>
-          ${columns.map((p) => {
-            return this._predObjsBlock(subj, p);
-          })}
-        </div>
-      </div>
-    `;
-  }
-
-  _objCell(obj: Term) {
-    return html`
-      <div class="object">
-        ${this.nodeDisplay.render(obj)}
-        <!-- indicate what source or graph said this stmt -->
-      </div>
-    `;
-  }
-
-  _predObjsBlock(subj: NamedNode, pred: NamedNode) {
-    const objsSet = new Set<Term>();
-    this.graph.forEach(
-      (q: Quad) => {
-        objsSet.add(q.object);
-      },
-      subj,
-      pred,
-      null,
-      null
-    );
-    const objs = Array.from(objsSet.values());
-    objs.sort();
-    return html`
-      <div class="predicate">
-        ${this.nodeDisplay.render(pred)}
-        <div>${objs.map(this._objCell.bind(this))}</div>
-      </div>
-    `;
-  }
-
-  _drawObj(obj: Term): TemplateResult {
-    return html` <div>${this.nodeDisplay.render(obj)}</div> `;
-  }
-
-  _drawColumnHead(pred: NamedNode): TemplateResult {
-    return html` <th>${this.nodeDisplay.render(pred)}</th> `;
-  }
-
-  _thead(layout: MultiSubjsTypeBlockLayout): TemplateResult {
-    return html`
-      <thead>
-        <tr>
-          <th></th>
-          ${layout.preds.map(this._drawColumnHead.bind(this))}
-        </tr>
-      </thead>
-    `;
-  }
-
-  _msbCell(layout: MultiSubjsTypeBlockLayout, subj: NamedNode) {
-    return (pred: NamedNode): TemplateResult => {
-      const objs = layout.graphCells.get(layout.makeCellKey(subj, pred));
-      if (!objs || !objs.size) {
-        return html` <td></td> `;
-      }
-      const objsList = Array.from(objs);
-      objsList.sort();
-      return html` <td>${objsList.map(this._drawObj.bind(this))}</td> `;
-    };
-  }
-
-  _instanceRow(layout: MultiSubjsTypeBlockLayout) {
-    return (subj: NamedNode): TemplateResult => {
-      return html`
-        <tr>
-          <td>${this.nodeDisplay.render(subj)}</td>
-          ${layout.preds.map(this._msbCell(layout, subj))}
-        </tr>
-      `;
-    };
-  }
-
-  _multiSubjsTypeBlock(byType: TypeToSubjs, table: TableDesc) {
-    const layout = new MultiSubjsTypeBlockLayout(this.graph, byType, table);
-
-    let typeNames = [html`${this.nodeDisplay.render(table.primary)}`];
-    if (table.joins) {
-      typeNames.push(html` joined with [`);
-      for (let j of table.joins) {
-        typeNames.push(html`${this.nodeDisplay.render(j)}`);
-      }
-      typeNames.push(html`]`);
-    }
-
-    return html`
-      <div>[icon] Resources of type ${typeNames}</div>
-      <div class="typeBlockScroll">
-        <table class="typeBlock">
-          ${this._thead(layout)} ${layout.subjs.map(this._instanceRow(layout))}
-        </table>
-      </div>
-    `;
-  }
-
-  async makeTemplate(): Promise<TemplateResult> {
-    await this.view.ready;
-    const { byType, typesPresent, untypedSubjs } = groupByRdfType(this.graph);
-    let viewTitle = html` (no view)`;
-    if (this.view.url) {
-      viewTitle = html` using view
-        <a href="${this.view.url}">${this.view.label()}</a>`;
-    }
-    const tables = this.view.toplevelTables(typesPresent);
-    return html`
-      <section>
-        <h2>
-          Current graph (<a href="${this.url}">${this.url}</a>)${viewTitle}
-        </h2>
-        <div>
-          <!-- todo: graphs and provenance.
-            These statements are all in the
-            <span data-bind="html: $root.createCurie(graphUri())">...</span> graph.-->
-        </div>
-        ${tables.map((t: TableDesc) => this._multiSubjsTypeBlock(byType, t))}
-        <div class="spoGrid">
-          ${untypedSubjs.map(this._subjPredObjsBlock.bind(this))}
-        </div>
-      </section>
-    `;
-  }
-}