changeset 110:3cdbbd913f1d

table displays now just barely
author drewp@bigasterisk.com
date Fri, 18 Mar 2022 23:41:24 -0700
parents cbcd82d21356
children f08004002444
files src/layout/Layout.ts src/layout/ViewConfig.ts src/layout/namespaces.ts src/render/GraphView.ts src/render/StreamedGraph.ts src/render/style.ts
diffstat 6 files changed, 136 insertions(+), 67 deletions(-) [+]
line wrap: on
line diff
--- a/src/layout/Layout.ts	Fri Mar 18 12:00:33 2022 -0700
+++ b/src/layout/Layout.ts	Fri Mar 18 23:41:24 2022 -0700
@@ -2,7 +2,7 @@
 
 import Immutable from "immutable"; // mostly using this for the builtin equals() testing, since NamedNode(x)!=NamedNode(x)
 import { NamedNode, Quad, Store, Term } from "n3";
-import { rdf } from "./namespaces";
+import { rdf, rdfs } from "./namespaces";
 import { uniqueSortedTerms } from "./rdf_value";
 import { TableDesc, ViewConfig } from "./ViewConfig";
 
@@ -16,7 +16,8 @@
 
 export interface AlignedTable {
   columnHeaders: ColumnHeader[];
-  rows: (Term | null)[][]; // each row is 1 wider than columnHeaders since the 1st element is the subject for that row
+  rowHeaders: NamedNode[];
+  rows: Term[][][];
 }
 
 export interface PredRow {
@@ -37,51 +38,72 @@
   sections: (AlignedTable | FreeStatements)[];
 }
 
+interface ISP {
+  subj: NamedNode;
+  pred: NamedNode;
+}
+const makeSP = Immutable.Record<ISP>({
+  subj: new NamedNode(""),
+  pred: new NamedNode(""),
+});
+
 class AlignedTableBuilder {
-  columnPreds = Immutable.List<NamedNode>();
-  subjRowIndices = Immutable.Map<NamedNode, number>();
-  rows: (Term | null)[][] = [];
+  subjSet = Immutable.Set<NamedNode>();
+  predSet = Immutable.Set<NamedNode>();
+  cell = Immutable.Map<string, Immutable.Set<Term>>();
   constructor(
     public rdfType: NamedNode /* plus join types, sort instructions */
   ) {}
 
   addQuad(q: Quad) {
+    const subj = q.subject as NamedNode;
     const pred = q.predicate as NamedNode;
-    const omittedColumn = pred.equals(rdf.type);
-    if (!this.columnPreds.contains(pred) && !omittedColumn) {
-      this.columnPreds = this.columnPreds.push(pred); // this is putting cols in random order
-      this.rows.forEach((r) => r.push(null));
+    this.subjSet = this.subjSet.add(subj);
+    this.predSet = this.predSet.add(pred);
+
+    const key =subj.id+pred.id//makeSP({ subj, pred });
+    const cur = this.cell.get(key, undefined);
+    const newval =
+      cur === undefined ? Immutable.Set([q.object]) : cur.add(q.object);
+
+    this.cell = this.cell.set(key, newval);
+  }
+  
+  value(): AlignedTable {
+    let preds = uniqueSortedTerms(this.predSet);
+    const tagged = preds.map((p, i)=>{
+      if (p.equals(rdf.type)) {
+        i=999;
+      }
+      if (p.equals(rdfs.label)) {
+        i=-1
+      }
+      return {sort:i, val: p}
+    })
+    tagged.sort((a,b)=>{
+      return a.sort - b.sort;
+    });
+    preds = tagged.map((e)=>e.val);
+
+    // const omittedColumn = pred.equals(rdf.type);
+    const subjs = uniqueSortedTerms(this.subjSet);
+    const outputGrid: Term[][][] = [];
+    for (let subj of subjs) {
+      const row: Term[][] = [];
+      preds.forEach((pred) => {
+        const key = subj.id+pred.id;//makeSP({ subj, pred });
+        const objs = this.cell.get(key, Immutable.Set<Term>([]));
+        const uniq = uniqueSortedTerms(objs);
+        console.log("cell objs", objs.size, uniq.length);
+        row.push(uniq);
+      });
+      outputGrid.push(row);
     }
 
-    const predIndex = omittedColumn ? null : this.columnPreds.indexOf(pred);
-    let rowIndex = this.subjRowIndices.get(q.subject as NamedNode);
-    if (rowIndex === undefined) {
-      const newRow = new Array(1 + this.columnPreds.size).fill(null);
-      newRow[0] = q.subject;
-      this.rows.push(newRow);
-      rowIndex = this.rows.length - 1;
-      this.subjRowIndices = this.subjRowIndices.set(
-        q.subject as NamedNode,
-        rowIndex
-      );
-    }
-    if (predIndex !== null) {
-      this.rows[rowIndex][1 + predIndex] = q.object;
-    }
-  }
-
-  value(): AlignedTable {
-    this.rows.sort((a, b) => {
-      const uriA = (a[0] as NamedNode).value,
-        uriB = (b[0] as NamedNode).value;
-      return uriA.localeCompare(uriB);
+    const headers = preds.map((pred) => {
+      return { rdfType: this.rdfType, pred: pred };
     });
-    const headers = this.columnPreds
-      .map((pred) => {
-        return { rdfType: this.rdfType, pred: pred };
-      })
-      .toArray();
-    return { columnHeaders: headers, rows: this.rows };
+    return { columnHeaders: headers, rowHeaders: subjs, rows: outputGrid };
   }
 }
 
@@ -95,11 +117,13 @@
   return Immutable.Set(typesToGather);
 }
 
-function findSubjectsWithTypes(graph: Store, typesToGatherSet: UriSet): UriSet {
+function findSubjectsWithTypes(graph: Store, typesToGather: UriSet): UriSet {
   const subjectsToGather: NamedNode[] = [];
+  const ft = typesToGather.toArray()[0];
   graph.forEach(
     (q: Quad) => {
-      if (typesToGatherSet.contains(q.object as NamedNode)) {
+      if (q.object.equals(ft)) {
+        //typesToGather.has(q.object as NamedNode)) {
         subjectsToGather.push(q.subject as NamedNode);
       }
     },
@@ -142,22 +166,30 @@
 export class Layout {
   constructor(public viewConfig?: ViewConfig) {}
   plan(graph: Store): LayoutResult {
-    const typesToGatherSet = findTypesNeededForTables(this.viewConfig);
+    const typesToTable = findTypesNeededForTables(this.viewConfig);
 
-    const subjectsToGatherSet = findSubjectsWithTypes(graph, typesToGatherSet);
+    const subjectsToTable = findSubjectsWithTypes(graph, typesToTable);
     const ungrouped: Quad[] = [];
     const vc = this.viewConfig;
     const table =
       vc && vc.tables.length > 0
-        ? new AlignedTableBuilder(vc.tables[0].primary)
+        ? new AlignedTableBuilder(vc.tables[0].primary) //todo multiple tables
         : null;
 
     graph.forEach(
       (q: Quad) => {
-        if (!subjectsToGatherSet.contains(q.subject as NamedNode) || !table) {
+        let contains = false;
+        subjectsToTable.forEach((s) => {
+          if (s.equals(q.subject)) {
+            contains = true;
+          }
+        });
+
+        // if (subjectsToTable.has(q.subject as NamedNode) && table) { // not working
+        if (contains && table) {
+          table.addQuad(q);
+        } else {
           ungrouped.push(q);
-        } else {
-          table.addQuad(q);
         }
       },
       null,
@@ -167,6 +199,7 @@
     );
     const res: LayoutResult = { sections: [] };
     if (table) {
+      console.log("table value");
       res.sections.push(table.value());
     }
     res.sections.push(freeStatmentsSection(ungrouped));
@@ -174,15 +207,6 @@
   }
 }
 
-// interface ISP {
-//   subj: NamedNode;
-//   pred: NamedNode;
-// }
-// const SP = Immutable.Record<ISP>({
-//   subj: new NamedNode(""),
-//   pred: new NamedNode(""),
-// });
-
 // // One table of rows with a common rdf:type.
 // export class MultiSubjsTypeBlockLayout {
 //   subjs: NamedNode[];
--- a/src/layout/ViewConfig.ts	Fri Mar 18 12:00:33 2022 -0700
+++ b/src/layout/ViewConfig.ts	Fri Mar 18 23:41:24 2022 -0700
@@ -32,6 +32,7 @@
     if (!url) {
       return;
     }
+    this.url = url;
     await fetchAndParse(url, this.graph);
 
     this._read();
--- a/src/layout/namespaces.ts	Fri Mar 18 12:00:33 2022 -0700
+++ b/src/layout/namespaces.ts	Fri Mar 18 23:41:24 2022 -0700
@@ -6,6 +6,8 @@
 export const EX = Util.prefix("http://example.com/");
 
 export const rdf = {
-    type: namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
-  };
-  
\ No newline at end of file
+  type: namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
+};
+export const rdfs = {
+  label: namedNode("http://www.w3.org/2000/01/rdf-schema#label"),
+};
--- a/src/render/GraphView.ts	Fri Mar 18 12:00:33 2022 -0700
+++ b/src/render/GraphView.ts	Fri Mar 18 23:41:24 2022 -0700
@@ -1,7 +1,13 @@
 import Immutable from "immutable";
 import { html, TemplateResult } from "lit";
 import { NamedNode, Quad, Store, Term } from "n3";
-import { AlignedTable, FreeStatements, Layout, PredRow, SubjRow } from "../layout/Layout";
+import {
+  AlignedTable,
+  FreeStatements,
+  Layout,
+  PredRow,
+  SubjRow,
+} from "../layout/Layout";
 import { SuffixLabels } from "../layout/suffixLabels";
 import { ViewConfig } from "../layout/ViewConfig";
 import { NodeDisplay } from "./NodeDisplay";
@@ -27,9 +33,8 @@
     let viewTitle = html` (no view)`;
     if (this.viewConfig?.url) {
       viewTitle = html` using view
-        <a href="${this.viewConfig.url}">${this.viewConfig.label()}</a>`;
+        <a href="${this.viewConfig?.url}">${this.viewConfig?.label()}</a>`;
     }
-    // const tables = this.view.toplevelTables(typesPresent);
     return html`
       <section>
         <h2>
@@ -78,27 +83,54 @@
   }
 
   _renderAlignedTable(section: AlignedTable): TemplateResult {
-    return html`aligned table section`;
+    const heads = section.columnHeaders.map((ch) => {
+      return html`<th>${this.nodeDisplay.render(ch.pred)}</th>`;
+    });
+    const cells = [];
+
+    for (let rowIndex in section.rows) {
+      const headerCol = html`<th>${this.nodeDisplay.render(section.rowHeaders[rowIndex])}</th>`;
+      const bodyCols = []
+      for (let cellObjs of section.rows[rowIndex]) {
+        const display = cellObjs.map(
+          (t) => html`<div>${this.nodeDisplay.render(t)}</div>`
+        );
+        bodyCols.push(html`<td>${display}</td>`);
+      }
+      cells.push(html`<tr>${headerCol}${bodyCols}</tr>`);
+    }
+    return html`
+      <pre> {JSON.stringify(section.rows,null,'   ')}</pre>
+      <div class="typeBlockScroll">
+      <table class="typeBlock">
+        <thead>
+          <th>Subject</th>
+          ${heads}
+        </thead>
+        <tbody>
+          ${cells}
+        </tbody>
+      </table>
+      </div>
+    `;
   }
 
   _renderFreeStatements(section: FreeStatements): TemplateResult {
     const subjects: NamedNode[] = [];
     let subjPreds = Immutable.Map<NamedNode, UriSet>();
-    
+
     return html`<div class="spoGrid">
-      grid has rowcount ${section.subjRows.length} 
+      grid has rowcount ${section.subjRows.length}
       ${section.subjRows.map(this._subjPredObjsBlock.bind(this))}
     </div>`;
   }
 
-  _subjPredObjsBlock( row: SubjRow ): TemplateResult {
+  _subjPredObjsBlock(row: SubjRow): TemplateResult {
     return html`
       <div class="subject">
         ${this.nodeDisplay.render(row.subj)}
         <!-- todo: special section for uri/type-and-icon/label/comment -->
-        <div>
-          ${row.predRows.map(this._predObjsBlock.bind(this))}
-        </div>
+        <div>${row.predRows.map(this._predObjsBlock.bind(this))}</div>
       </div>
     `;
   }
--- a/src/render/StreamedGraph.ts	Fri Mar 18 12:00:33 2022 -0700
+++ b/src/render/StreamedGraph.ts	Fri Mar 18 23:41:24 2022 -0700
@@ -105,6 +105,9 @@
 
     if ((this.graph as VersionedGraph).store && this.graph.store) {
       const vc = new ViewConfig();
+      if (this.view) {
+        await vc.readFromUrl(this.view); // too often!
+      }
 
       await this._graphAreaShowGraph(
         new GraphView([this.url], this.graph.store, vc)
--- a/src/render/style.ts	Fri Mar 18 12:00:33 2022 -0700
+++ b/src/render/style.ts	Fri Mar 18 23:41:24 2022 -0700
@@ -83,6 +83,13 @@
 #graphView table.typeBlock td {
   white-space: nowrap;
 }
+#graphView table tr:nth-child(even) td:nth-child(even) { background: #1a191c; }
+#graphView table tr:nth-child(even) td:nth-child(odd) { background: #181719; }
+#graphView table tr:nth-child(odd) td:nth-child(even) { background: #201e22; }
+#graphView table tr:nth-child(odd) td:nth-child(odd) { background: #1e1c1e; }
+#graphView table td,#graphView table th {
+  vertical-align:top;
+}
 #graphView table.typeBlock td .literal {
   padding-top: 1px;
   padding-bottom: 1px;