Refinements to basic graph editing
This commit is contained in:
parent
77d6698899
commit
dd582c4d20
|
@ -3,7 +3,11 @@ import { MermaidDiagram } from './mermaid.js';
|
|||
export class Flowchart extends MermaidDiagram {
|
||||
constructor(box, logBox, direction = 'BT') {
|
||||
super(box, logBox);
|
||||
this.direction = direction;
|
||||
this.init();
|
||||
}
|
||||
|
||||
this.log(`graph ${direction}`, false);
|
||||
init() {
|
||||
this.log(`graph ${this.direction}`, false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,4 +69,8 @@ export class MermaidDiagram {
|
|||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.logBoxPre.textContent = '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,8 +61,7 @@ export class Scene {
|
|||
const container = this.middleSection.addBox(name).flex();
|
||||
const box = container.addBox('Flowchart').addClass('padded');
|
||||
const logBox = container.addBox('Flowchart text').addClass('dim');
|
||||
const flowchart = new MermaidDiagram(box, logBox);
|
||||
flowchart.log(`graph ${direction}`, false);
|
||||
const flowchart = new Flowchart(box, logBox, direction);
|
||||
this.flowcharts.set(id, flowchart);
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,11 @@ export class Edge {
|
|||
this.weight = weight;
|
||||
this.data = data;
|
||||
this.options = options;
|
||||
this.installedClickCallback = false;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.installedClickCallback = false;
|
||||
}
|
||||
|
||||
static getKey({
|
||||
|
@ -49,8 +54,9 @@ export class Edge {
|
|||
}
|
||||
this.graph.flowchart?.log(`${this.from.id} --- ${this.getFlowchartNode()} --> ${this.to.id}`);
|
||||
this.graph.flowchart?.log(`class ${Edge.getCombinedKey(this)} edge`);
|
||||
if (this.graph.editable) {
|
||||
if (this.graph.editable && !this.installedClickCallback) {
|
||||
this.graph.flowchart?.log(`click ${Edge.getCombinedKey(this)} WDGHandler${this.graph.index} "Edit Edge"`);
|
||||
this.installedClickCallback = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,19 +106,17 @@ export class Edge {
|
|||
name: 'Save',
|
||||
cb: ({ form: { value } }, { initializing }) => {
|
||||
if (initializing) return;
|
||||
console.log('graph before save', { ...graph });
|
||||
// Handle additions and updates
|
||||
for (const { type, weight } of value.edges) {
|
||||
graph.setEdgeWeight(type, from, to, weight);
|
||||
}
|
||||
// Handle removals
|
||||
for (const edgeType of graph.edgeTypes.values()) {
|
||||
if (!value.edges.find((edge) => edge.type === edgeType)) {
|
||||
graph.deleteEdge(edgeType, from, to);
|
||||
for (const edge of graph.getEdges(null, from, to)) {
|
||||
if (!value.edges.find(({ type }) => type === edge.type)) {
|
||||
graph.deleteEdge(edge.type, from, to);
|
||||
}
|
||||
}
|
||||
console.log('graph after save', { ...graph });
|
||||
// TODO: Redraw graph
|
||||
graph.redraw();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -12,6 +12,10 @@ export class Vertex {
|
|||
this.installedClickCallback = false;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.installedClickCallback = false;
|
||||
}
|
||||
|
||||
set id(newId) {
|
||||
this._id = newId;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,8 @@ import { Edge } from './edge.js';
|
|||
|
||||
const graphs = [];
|
||||
|
||||
const makeWDGHandler = (graph) => (vertexId) => {
|
||||
const makeWDGHandler = (graphIndex) => (vertexId) => {
|
||||
const graph = graphs[graphIndex];
|
||||
// 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;
|
||||
|
@ -28,7 +29,62 @@ export class WeightedDirectedGraph {
|
|||
// 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);
|
||||
window[`WDGHandler${this.index}`] = makeWDGHandler(this.index);
|
||||
this.history = {};
|
||||
}
|
||||
|
||||
getHistory() {
|
||||
// record operations that modify the graph
|
||||
return this.history;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
vertices: Array.from(this.vertices.values()),
|
||||
edgeTypes: Array.from(this.edgeTypes.keys()),
|
||||
edges: Array.from(this.edgeTypes.values()).flatMap((edges) => Array.from(edges.values())),
|
||||
history: this.getHistory(),
|
||||
};
|
||||
}
|
||||
|
||||
redraw() {
|
||||
// Call .reset() on all vertices and edges
|
||||
for (const vertex of this.vertices.values()) {
|
||||
vertex.reset();
|
||||
}
|
||||
for (const edges of this.edgeTypes.values()) {
|
||||
for (const edge of edges.values()) {
|
||||
edge.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the target div
|
||||
this.flowchart?.reset();
|
||||
this.flowchart?.init();
|
||||
|
||||
// Draw all vertices and edges
|
||||
for (const vertex of this.vertices.values()) {
|
||||
vertex.displayVertex();
|
||||
}
|
||||
// Let's flatmap and dedupe by [from, to] since each edge
|
||||
// renders all comorphic edges as well.
|
||||
const edgesFrom = new Map(); // edgeMap[from][to] = edge
|
||||
for (const edges of this.edgeTypes.values()) {
|
||||
for (const edge of edges.values()) {
|
||||
const edgesTo = edgesFrom.get(edge.from) || new Map();
|
||||
edgesTo.set(edge.to, edge);
|
||||
edgesFrom.set(edge.from, edgesTo);
|
||||
}
|
||||
}
|
||||
|
||||
for (const edgesTo of edgesFrom.values()) {
|
||||
for (const edge of edgesTo.values()) {
|
||||
edge.displayEdge();
|
||||
}
|
||||
}
|
||||
|
||||
// Rerender
|
||||
this.flowchart?.render();
|
||||
}
|
||||
|
||||
withFlowchart() {
|
||||
|
@ -88,6 +144,9 @@ export class WeightedDirectedGraph {
|
|||
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);
|
||||
if (edges.size === 0) {
|
||||
this.edgeTypes.delete(type);
|
||||
}
|
||||
}
|
||||
|
||||
getEdgeWeight(type, from, to) {
|
||||
|
@ -112,13 +171,18 @@ export class WeightedDirectedGraph {
|
|||
addEdge(type, from, to, weight, data, options) {
|
||||
from = from instanceof Vertex ? from : this.getVertex(from);
|
||||
to = to instanceof Vertex ? to : this.getVertex(to);
|
||||
const existingEdges = this.getEdges(type, from, 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();
|
||||
if (existingEdges.length) {
|
||||
edge.displayEdgeNode();
|
||||
} else {
|
||||
edge.displayEdge();
|
||||
}
|
||||
return edge;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue