// deck.gl-community // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import type {NodeState} from '../core/constants'; import type {EdgeInterface, NodeInterface} from './graph'; /** Properties for creating a new node */ export interface NodeOptions { /** the unique ID of the node */ id: number | string; selectable?: boolean; highlightConnectedEdges?: boolean; /* origin data reference */ data?: Record; } /** Basic data structure of a node */ export class Node implements NodeInterface { public readonly id: string | number; /** Keep a reference to origin data. */ private _data: Record; /** List edges. */ private _connectedEdges: Record = {}; /** Interaction state of the node. */ public state: NodeState = 'default'; /** Can the node be selected? */ private _selectable: boolean; /** Should the state of this node affect the state of the connected edges? */ private _highlightConnectedEdges: boolean; /** Check the type of the object when picking engine gets it. */ public readonly isNode = true; /** * The constructor of a node */ constructor({id, selectable = false, highlightConnectedEdges = false, data = {}}: NodeOptions) { this.id = id; this._data = data; this._selectable = selectable; this._highlightConnectedEdges = highlightConnectedEdges; } /** * Return the ID of the node * @return - the ID of the node. */ getId(): string | number { return this.id; } /** * Return the degree of the node -- includes in-degree and out-degree * @return {Number} - the degree of the node. */ getDegree(): number { return Object.keys(this._connectedEdges).length; } /** * Return the in-degree of the node. * @return - the in-degree of the node. */ getInDegree(): number { const nodeId = this.getId(); return this.getConnectedEdges().reduce((count, e) => { const isDirected = e.isDirected(); if (isDirected && e.getTargetNodeId() === nodeId) { count += 1; } return count; }, 0); } /** * Return the out-degree of the node. * @return - the out-degree of the node. */ getOutDegree(): number { const nodeId = this.getId(); return this.getConnectedEdges().reduce((count, e) => { const isDirected = e.isDirected(); if (isDirected && e.getSourceNodeId() === nodeId) { count += 1; } return count; }, 0); } /** * Return all the IDs of the sibling nodes. * @return [description] */ getSiblingIds(): (string | number)[] { const nodeId = this.getId(); return this.getConnectedEdges().reduce( (siblings, e) => { if (e.getTargetNodeId() === nodeId) { siblings.push(e.getSourceNodeId()); } else { siblings.push(e.getTargetNodeId()); } return siblings; }, [] as (string | number)[] ); } /** * Return all the connected edges. * @return - an array of the connected edges. */ getConnectedEdges(): EdgeInterface[] { return Object.values(this._connectedEdges); } /** * Return of the value of the selected property key. * @param key - property key. * @return - the value of the property or undefined (not found). */ getPropertyValue(key: string): unknown { // try to search the key within this object if (this.hasOwnProperty(key)) { return this[key]; } // try to search the key in the original data reference else if (this._data.hasOwnProperty(key)) { return this._data[key]; } // otherwise, not found return undefined; } /** * Set the new node data. * @param data - the new data of the node */ setData(data: Record): void { this._data = data; } /** * Update a data property. * @param key - the key of the property * @param value - the value of the property. */ setDataProperty(key: string, value: unknown): void { this._data[key] = value; } /** * Set node state * @param state - the new interaction state of the node */ setState(state: NodeState): void { this.state = state; } /** * Get node state * @returns state - the current interaction state of the node */ getState(): NodeState { return this.state; } /** * Add connected edges to the node * @param edge an edge or an array of edges to be added to this._connectedEdges */ addConnectedEdges(edge: EdgeInterface | EdgeInterface[]): void { const iterableEdges = Array.isArray(edge) ? edge : [edge]; iterableEdges.forEach(e => { this._connectedEdges[e.getId()] = e; e.addNode(this); }); } /** * Remove edges from this._connectedEdges * @param edge an edge or an array of edges to be removed from this._connectedEdges */ removeConnectedEdges(edge: EdgeInterface | EdgeInterface[]): void { const iterableEdges = Array.isArray(edge) ? edge : [edge]; iterableEdges.forEach(e => { e.removeNode(this); delete this._connectedEdges[e.getId()]; }); } /** * Clear this._connectedEdges */ clearConnectedEdges(): void { Object.values(this._connectedEdges).forEach(e => e.removeNode(this)); this._connectedEdges = {}; } isSelectable(): boolean { return this._selectable; } shouldHighlightConnectedEdges(): boolean { return this._highlightConnectedEdges; } }