dao-governance-framework/forum-network/src/classes/supporting/wdg.js

146 lines
4.7 KiB
JavaScript

import { Vertex } from './vertex.js';
import { Edge } from './edge.js';
const graphs = [];
const makeWDGHandler = (graph) => (vertexId) => {
// We want a document for editing this node, which may be a vertex or an edge
const editorDoc = graph.scene.getDocument('editorDocument')
?? graph.scene.withDocument('editorDocument').lastDocument;
if (vertexId.startsWith('edge:')) {
const [, from, to] = vertexId.split(':');
Edge.prepareEditorDocument(graph, editorDoc, from, to);
} else {
Vertex.prepareEditorDocument(graph, editorDoc, vertexId);
}
};
export class WeightedDirectedGraph {
constructor(scene, options = {}) {
this.scene = scene;
this.vertices = new Map();
this.edgeTypes = new Map();
this.nextVertexId = 0;
this.flowchart = scene?.flowchart;
this.editable = options.editable;
this.index = graphs.length;
graphs.push(this);
// Mermaid supports a click callback, but we can't customize arguments; we just get the vertex ID.
// In order to provide the appropriate graph context for each callback, we create a separate callback
// function for each graph.
window[`WDGHandler${this.index}`] = makeWDGHandler(this);
}
withFlowchart() {
this.scene?.withAdditionalFlowchart();
this.flowchart = this.scene?.lastFlowchart();
return this;
}
addVertex(type, id, data, label, options) {
// Supports simple case of auto-incremented numeric ids
if (typeof id === 'object') {
data = id;
id = this.nextVertexId++;
}
id = (typeof id === 'number') ? id.toString(10) : id;
if (this.vertices.has(id)) {
throw new Error(`Vertex already exists with id: ${id}`);
}
const vertex = new Vertex(this, type, id, data, options);
this.vertices.set(id, vertex);
vertex.setDisplayLabel(label ?? id);
return vertex;
}
getVertex(id) {
id = (typeof id === 'number') ? id.toString(10) : id;
return this.vertices.get(id);
}
getVertexData(id) {
return this.getVertex(id)?.data;
}
getVerticesData() {
return Array.from(this.vertices.values()).map(({ data }) => data);
}
getEdge(type, from, to) {
from = from instanceof Vertex ? from : this.getVertex(from);
to = to instanceof Vertex ? to : this.getVertex(to);
if (!from || !to) {
return undefined;
}
const edges = this.edgeTypes.get(type);
const edgeKey = Edge.getKey({ from, to, type });
return edges?.get(edgeKey);
}
deleteEdge(type, from, to) {
from = from instanceof Vertex ? from : this.getVertex(from);
to = to instanceof Vertex ? to : this.getVertex(to);
const edges = this.edgeTypes.get(type);
const edgeKey = Edge.getKey({ type, from, to });
if (!edges) return;
const edge = edges.get(edgeKey);
if (!edge) return;
to.edges.from.forEach((x, i) => (x === edge) && to.edges.from.splice(i, 1));
from.edges.to.forEach((x, i) => (x === edge) && from.edges.to.splice(i, 1));
edges.delete(edgeKey);
}
getEdgeWeight(type, from, to) {
return this.getEdge(type, from, to)?.weight;
}
setEdgeWeight(type, from, to, weight, data, options) {
from = from instanceof Vertex ? from : this.getVertex(from);
to = to instanceof Vertex ? to : this.getVertex(to);
const edge = new Edge(this, type, from, to, weight, data, options);
let edges = this.edgeTypes.get(type);
if (!edges) {
edges = new Map();
this.edgeTypes.set(type, edges);
}
const edgeKey = Edge.getKey(edge);
edges.set(edgeKey, edge);
edge.displayEdgeNode();
return edge;
}
addEdge(type, from, to, weight, data, options) {
from = from instanceof Vertex ? from : this.getVertex(from);
to = to instanceof Vertex ? to : this.getVertex(to);
if (this.getEdge(type, from, to)) {
throw new Error(`Edge ${type} from ${from.id} to ${to.id} already exists`);
}
const edge = this.setEdgeWeight(type, from, to, weight, data, options);
from.edges.from.push(edge);
to.edges.to.push(edge);
edge.displayEdge();
return edge;
}
getEdges(type, from, to) {
from = from instanceof Vertex ? from : this.getVertex(from);
to = to instanceof Vertex ? to : this.getVertex(to);
const edgeTypes = type ? [type] : Array.from(this.edgeTypes.keys());
return edgeTypes.flatMap((edgeType) => {
const edges = this.edgeTypes.get(edgeType);
return Array.from(edges?.values() || []).filter((edge) => {
const matchFrom = from === null || from === undefined || from === edge.from;
const matchTo = to === null || to === undefined || to === edge.to;
return matchFrom && matchTo;
});
});
}
countVertices(type) {
if (!type) {
return this.vertices.size;
}
return Array.from(this.vertices.values()).filter((vertex) => vertex.type === type).length;
}
}