UNPKG

@reactodia/workspace

Version:

Reactodia Workspace -- library for visual interaction with graphs in a form of a diagram.

233 lines (212 loc) 6.48 kB
import { chainHash, dropHighestNonSignBit, hashString } from '@reactodia/hashmap'; import { shallowArrayEqual } from '../coreUtils/collections'; import * as Rdf from './rdf/rdfModel'; /** * Nominal (branded) type for element (graph node) IRI, i.e. unique ID string. */ export type ElementIri = string & { readonly __iriBrand?: 'element' }; /** * Nominal (branded) type for element (graph node) type IRI, i.e. unique ID string. */ export type ElementTypeIri = string & { readonly __iriBrand?: 'elementType' }; /** * Nominal (branded) type for link (graph edge) type IRI, i.e. unique ID string. */ export type LinkTypeIri = string & { readonly __iriBrand?: 'linkType' }; /** * Nominal (branded) type for property type IRI, i.e. unique ID string. */ export type PropertyTypeIri = string & { readonly __iriBrand?: 'propertyType' }; /** * Link (graph edge) direction: `in` for incoming, `out` for outgoing. */ export type LinkDirection = 'in' | 'out'; /** * Describes a graph of element types (nodes) and "subtype of" relations * between them (edges). * * @category Data */ export interface ElementTypeGraph { readonly elementTypes: ReadonlyArray<ElementTypeModel>; readonly subtypeOf: ReadonlyArray<SubtypeEdge>; } /** * "Subtype of" relation between derived element type and its base type. * * @category Data * @see {@link ElementTypeGraph} */ export type SubtypeEdge = readonly [derived: ElementTypeIri, base: ElementTypeIri]; /** * Element (graph node) data. * * @category Data */ export interface ElementModel { readonly id: ElementIri; readonly types: ReadonlyArray<ElementTypeIri>; readonly properties: { readonly [id: string]: ReadonlyArray<Rdf.NamedNode | Rdf.Literal> }; } /** * A `{source, target, type}` tuple which uniquely identifies a link (graph edge). * * @category Data */ export interface LinkKey { readonly linkTypeId: LinkTypeIri; readonly sourceId: ElementIri; readonly targetId: ElementIri; } /** * Link (graph edge) data. * * @category Data */ export interface LinkModel { readonly linkTypeId: LinkTypeIri; readonly sourceId: ElementIri; readonly targetId: ElementIri; readonly properties: { readonly [id: string]: ReadonlyArray<Rdf.NamedNode | Rdf.Literal> }; } /** * Element (graph node) type data. * * @category Data */ export interface ElementTypeModel { readonly id: ElementTypeIri; readonly label: ReadonlyArray<Rdf.Literal>; readonly count?: number; } /** * Link (graph edge) type data. * * @category Data */ export interface LinkTypeModel { readonly id: LinkTypeIri; readonly label: ReadonlyArray<Rdf.Literal>; readonly count?: number; } /** * Property type data. * * @category Data */ export interface PropertyTypeModel { readonly id: PropertyTypeIri; readonly label: ReadonlyArray<Rdf.Literal>; } /** * Returns `true` if IRI represents an anonymous entity specific to the data provider; * otherwise `false`. * * The represented entity can only be decoded by a {@link DataProvider} with a support * for the specific blank node subtype, determined by the IRI prefix, e.g.: * - `urn:reactodia:blank:rdf:*` encodes RDF blank nodes from {@link RdfDataProvider}; * - `urn:reactodia:blank:sparql:*` encodes outer graph content for blank nodes * from {@link SparqlDataProvider}; * - etc. * * @category Data */ export function isEncodedBlank(iri: string): boolean { return iri.startsWith('urn:reactodia:blank:'); } /** * Computes a hash code for {@link SubtypeEdge} value. * * @category Data */ export function hashSubtypeEdge(edge: SubtypeEdge): number { const [from, to] = edge; let hash = hashString(from); hash = chainHash(hash, hashString(to)); return dropHighestNonSignBit(hash); } /** * Computes whether {@link SubtypeEdge} values are the same. * * @category Data */ export function equalSubtypeEdges(a: SubtypeEdge, b: SubtypeEdge): boolean { const [aFrom, aTo] = a; const [bFrom, bTo] = b; return aFrom === bFrom && aTo === bTo; } /** * Computes whether {@link LinkKey} values are the same. * * @category Data */ export function equalLinks(left: LinkKey, right: LinkKey) { return ( left.linkTypeId === right.linkTypeId && left.sourceId === right.sourceId && left.targetId === right.targetId ); } /** * Computes a hash code for {@link LinkKey} value. * * @category Data */ export function hashLink(link: LinkKey): number { const {linkTypeId, sourceId, targetId} = link; let hash = hashString(linkTypeId); hash = chainHash(hash, hashString(sourceId)); hash = chainHash(hash, hashString(targetId)); return hash; } /** * Computes whether {@link ElementModel} values are the same, including property values. * * @category Data */ export function equalElements(a: ElementModel, b: ElementModel): boolean { return ( a.id === b.id && shallowArrayEqual(a.types, b.types) && equalProperties(a.properties, b.properties) ); } function equalTermArrays(a: ReadonlyArray<Rdf.Term>, b: ReadonlyArray<Rdf.Term>): boolean { if (a.length !== b.length) { return false; } for (let i = 0; i < a.length; i++) { if (!Rdf.equalTerms(a[i], b[i])) { return false; } } return true; } /** * Computes whether two property sets are the same, including both keys and values. * * @category Data */ export function equalProperties( a: { readonly [id: string]: ReadonlyArray<Rdf.NamedNode | Rdf.Literal> }, b: { readonly [id: string]: ReadonlyArray<Rdf.NamedNode | Rdf.Literal> } ) { for (const key in a) { if (Object.prototype.hasOwnProperty.call(a, key)) { if (!Object.prototype.hasOwnProperty.call(b, key)) { return false; } const aValues = a[key]; const bValues = b[key]; if (!equalTermArrays(aValues, bValues)) { return false; } } } for (const key in b) { if (Object.prototype.hasOwnProperty.call(b, key)) { if (!Object.prototype.hasOwnProperty.call(a, key)) { return false; } } } return true; }