view src/render/GraphView.ts @ 110:3cdbbd913f1d

table displays now just barely
author drewp@bigasterisk.com
date Fri, 18 Mar 2022 23:41:24 -0700
parents cbcd82d21356
children 4822d5621463
line wrap: on
line source

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 { SuffixLabels } from "../layout/suffixLabels";
import { ViewConfig } from "../layout/ViewConfig";
import { NodeDisplay } from "./NodeDisplay";

type UriSet = Immutable.Set<NamedNode>;

export class GraphView {
  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.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>`;
    }
    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) {
    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
    );
  }
  _renderSection(section: AlignedTable | FreeStatements) {
    if ((section as any).columnHeaders) {
      return this._renderAlignedTable(section as AlignedTable);
    } else {
      return this._renderFreeStatements(section as FreeStatements);
    }
  }

  _renderAlignedTable(section: AlignedTable): TemplateResult {
    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}
      ${section.subjRows.map(this._subjPredObjsBlock.bind(this))}
    </div>`;
  }

  _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>
    `;
  }

  _predObjsBlock(row: PredRow): TemplateResult {
    return html`
      <div class="predicate">
        ${this.nodeDisplay.render(row.pred)}
        <div>${row.objs.map(this._objCell.bind(this))}</div>
      </div>
    `;
  }

  _objCell(obj: Term): TemplateResult {
    return html`
      <div class="object">
        ${this.nodeDisplay.render(obj)}
        <!-- indicate what source or graph said this stmt -->
      </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>
  //   `;
  // }
}