diff 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
line wrap: on
line diff
--- a/src/render/GraphView.ts	Sun Mar 13 22:02:30 2022 -0700
+++ b/src/render/GraphView.ts	Fri Mar 18 11:57:38 2022 -0700
@@ -1,12 +1,16 @@
+import Immutable from "immutable";
 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 { AlignedTable, FreeStatements, Layout, PredRow, SubjRow } from "../layout/Layout";
 import { TableDesc, ViewConfig } from "../layout/ViewConfig";
+import { uniqueSortedTerms } from "../layout/rdf_value";
 
 const { namedNode } = DataFactory;
 
+type UriSet = Immutable.Set<NamedNode>;
+const emptyUriSet = Immutable.Set<NamedNode>();
 // https://github.com/rdfjs/N3.js/issues/265
 if ((Literal.prototype as any).hashCode === undefined) {
   (Literal.prototype as any).hashCode = () => 0;
@@ -15,22 +19,42 @@
   (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;
+  nodeDisplay!: NodeDisplay;
+  constructor(
+    public dataSourceUrls: string[],
+    public graph: Store,
+    public viewConfig?: ViewConfig
+  ) {}
+
+  async makeTemplate(): Promise<TemplateResult> {
+    const layout = new Layout(this.viewConfig);
+    const lr = layout.plan(this.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);
+    let viewTitle = html` (no view)`;
+    if (this.viewConfig?.url) {
+      viewTitle = html` using view
+        <a href="${this.viewConfig.url}">${this.viewConfig.label()}</a>`;
+    }
+    // const tables = this.view.toplevelTables(typesPresent);
+    return html`
+      <section>
+        <h2>
+          Current graph (<a href="${this.dataSourceUrls[0]}"
+            >${this.dataSourceUrls[0]}</a
+          >)${viewTitle}
+        </h2>
+        <div>
+          <!-- todo: graphs and provenance.
+            These statements are all in the
+            <span data-bind="html: $root.createCurie(graphUri())">...</span> graph.-->
+        </div>
+        ${lr.sections.map(this._renderSection.bind(this))}
+      </section>
+    `;
   }
 
   _addLabelsForAllTerms(graph: Store, labels: SuffixLabels) {
@@ -55,48 +79,54 @@
       null
     );
   }
+  _renderSection(section: AlignedTable | FreeStatements) {
+    if ((section as any).columnHeaders) {
+      return this._renderAlignedTable(section as AlignedTable);
+    } else {
+      return this._renderFreeStatements(section as FreeStatements);
+    }
+  }
 
-  _subjPredObjsBlock(subj: NamedNode) {
-    const columns = predsForSubj(this.graph, subj);
+  _renderAlignedTable(section: AlignedTable): TemplateResult {
+    return html`aligned table section`;
+  }
+
+  _renderFreeStatements(section: FreeStatements): TemplateResult {
+    const subjects: NamedNode[] = [];
+    let subjPreds = Immutable.Map<NamedNode, UriSet>();
+    
+    return html`<div class="spoGrid">
+      grid has rowcount ${section.subjRows.length} 
+      ${section.subjRows.map(this._subjPredObjsBlock.bind(this))}
+    </div>`;
+  }
+
+  _subjPredObjsBlock( row: SubjRow ): TemplateResult {
     return html`
       <div class="subject">
-        ${this.nodeDisplay.render(subj)}
+        ${this.nodeDisplay.render(row.subj)}
         <!-- todo: special section for uri/type-and-icon/label/comment -->
         <div>
-          ${columns.map((p) => {
-            return this._predObjsBlock(subj, p);
-          })}
+          ${row.predRows.map(this._predObjsBlock.bind(this))}
         </div>
       </div>
     `;
   }
 
-  _objCell(obj: Term) {
+  _predObjsBlock(row: PredRow): TemplateResult {
     return html`
-      <div class="object">
-        ${this.nodeDisplay.render(obj)}
-        <!-- indicate what source or graph said this stmt -->
+      <div class="predicate">
+        ${this.nodeDisplay.render(row.pred)}
+        <div>${row.objs.map(this._objCell.bind(this))}</div>
       </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();
+  _objCell(obj: Term): TemplateResult {
     return html`
-      <div class="predicate">
-        ${this.nodeDisplay.render(pred)}
-        <div>${objs.map(this._objCell.bind(this))}</div>
+      <div class="object">
+        ${this.nodeDisplay.render(obj)}
+        <!-- indicate what source or graph said this stmt -->
       </div>
     `;
   }
@@ -109,87 +139,59 @@
     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>
-    `;
-  }
+  // _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);
+  // _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> `;
+  //   };
+  // }
 
-    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`]`);
-    }
+  // _instanceRow(layout: MultiSubjsTypeBlockLayout) {
+  //   return (subj: NamedNode): TemplateResult => {
+  //     return html`
+  //       <tr>
+  //         <td>${this.nodeDisplay.render(subj)}</td>
+  //         ${layout.preds.map(this._msbCell(layout, subj))}
+  //       </tr>
+  //     `;
+  //   };
+  // }
 
-    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>
-    `;
-  }
+  // _multiSubjsTypeBlock(byType: TypeToSubjs, table: TableDesc) {
+  //   const layout = new MultiSubjsTypeBlockLayout(this.graph, byType, table);
 
-  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>
-    `;
-  }
+  //   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>
+  //   `;
+  // }
 }