view src/tabulate.ts @ 94:a5f53d397526

view: pick types to show at top-level
author drewp@bigasterisk.com
date Wed, 12 Jan 2022 22:09:20 -0800
parents ac7ad087d474
children 26c55d5d5202
line wrap: on
line source

// Organize graph data into tables (column orders, etc) for the view layer.

import Immutable from "immutable"; // mostly using this for the builtin equals() testing, since NamedNode(x)!=NamedNode(x)
import {
  DataFactory,
  NamedNode,
  Quad,
  Quad_Object,
  Store,
  Term,
  Util,
} from "n3";

const { namedNode } = DataFactory;

// // import ns from 'n3/src/IRIs';
// // const { rdf } = ns;
export const rdf = {
  type: namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
};

type UriSet = Immutable.Set<NamedNode>;
export type TypeToSubjs = Immutable.Map<NamedNode, UriSet>;

// https://github.com/rdfjs/N3.js/issues/265
if ((NamedNode.prototype as any).hashCode === undefined) {
  (NamedNode.prototype as any).hashCode = () => 0;
}

function getType(graph: Store, subj: NamedNode): NamedNode | null {
  let subjType: NamedNode | null = null;

  graph.forObjects(
    (o: Quad_Object) => {
      subjType = o as NamedNode;
    },
    subj,
    rdf.type,
    null
  );
  return subjType;
}

// When there are multiple types, an arbitrary one is used.
export function groupByRdfType(
  graph: Store
): { byType: TypeToSubjs; typesPresent: NamedNode[]; untypedSubjs: NamedNode[] } {
  let byType: TypeToSubjs = Immutable.Map();
  let untyped: UriSet = Immutable.Set(); // subjs
  const internSubjs = new Map<string, NamedNode>();
  graph.forEach(
    (q) => {
      if (!Util.isNamedNode(q.subject)) {
        throw new Error("unsupported " + q.subject.value);
      }
      const subj = q.subject as NamedNode;

      const subjType = getType(graph, subj);

      if (subjType !== null) {
        // (subj, rdf:type, subjType) in graph
        const oldKeys = Array.from(byType.keys());
        const oldVal = byType.get(subjType, Immutable.Set<NamedNode>());
        const newVal = oldVal.add(subj);
        byType = byType.set(subjType, newVal);
      } else {
        untyped = untyped.add(subj);
      }
    },
    null,
    null,
    null,
    null
  );

  const typesPresent = Array.from(byType.keys());
  typesPresent.sort();

  const untypedSubjs = Array.from(untyped.values());
  untypedSubjs.sort();
  return { byType: byType, typesPresent: typesPresent, untypedSubjs: untypedSubjs };
}

export function predsForSubj(graph: Store, typeUri: NamedNode): NamedNode[] {
  const predsSet: Set<NamedNode> = new Set();
  graph.forEach(
    (q: Quad) => {
      predsSet.add(q.predicate as NamedNode);
    },
    typeUri,
    null,
    null,
    null
  );
  const preds = Array.from(predsSet.values());
  preds.sort();
  return preds;
}

interface ISP {
  subj: NamedNode;
  pred: NamedNode;
}
const SP = Immutable.Record<ISP>({
  subj: new NamedNode(""),
  pred: new NamedNode(""),
});

export class MultiSubjsTypeBlockLayout {
  subjs: NamedNode[];
  preds: NamedNode[];
  graphCells: Immutable.Map<ISP, Immutable.Set<Term>>;
  constructor(graph: Store, byType: TypeToSubjs, typeUri: NamedNode) {
    const subjSet = byType.get(typeUri);
    this.subjs = subjSet ? Array.from(subjSet) : [];
    this.subjs.sort();

    let preds = Immutable.Set<NamedNode>();

    this.graphCells = Immutable.Map<ISP, Immutable.Set<Term>>().withMutations(
      (mutGraphCells) => {
        this.subjs.forEach((subj: NamedNode) => {
          graph.forEach(
            (q: Quad) => {
              if (!Util.isNamedNode(q.predicate)) {
                throw new Error();
              }

              const pred = q.predicate as NamedNode;
              if (pred.equals(rdf.type)) {
                // the whole block is labeled with the type
                return;
              }
              preds = preds.add(pred);
              const cellKey = this.makeCellKey(subj, pred);
              mutGraphCells.set(
                cellKey,
                mutGraphCells.get(cellKey, Immutable.Set<Term>()).add(q.object)
              );
            },
            subj,
            null,
            null,
            null
          );
        });
      }
    );
    this.preds = Array.from(preds);
    this.preds.splice(this.preds.indexOf(rdf.type), 1);
    // also pull out label, which should be used on 1st column
    this.preds.sort();
  }

  makeCellKey(subj: NamedNode, pred: NamedNode): ISP {
    return SP({
      subj: subj,
      pred: pred,
    });
  }
}