146 lines
4.7 KiB
JavaScript
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;
|
|
}
|
|
}
|