initial commit

This commit is contained in:
2025-11-07 22:45:28 +01:00
commit 5463923423
20 changed files with 6296 additions and 0 deletions

99
src/Graph.tsx Normal file
View File

@@ -0,0 +1,99 @@
import React, { useState } from "react";
import Viz from 'viz.js';
import { Module, render } from 'viz.js/full.render.js';
import { graphToDot } from "./Graphviz";
import * as d3 from 'd3';
import { NodeContextMenu } from "./NodeContextMenu";
const viz = new Viz({ Module, render });
export class GraphRenderer {
graph: Graph;
setGraph: React.Dispatch<React.SetStateAction<Graph>>;
containerRef: React.RefObject<null>;
contextMenu: NodeContextMenu;
constructor(containerRef: React.RefObject<null>) {
[this.graph, this.setGraph] = useState(defaultGraph());
this.containerRef = containerRef;
this.contextMenu = new NodeContextMenu(containerRef);
}
public async render() {
const dot = graphToDot(this.graph);
try {
const svgElement = await viz.renderSVGElement(dot, { engine: 'dot' });
const container = this.containerRef.current;
if (!container) return;
container.innerHTML = '';
container.appendChild(svgElement);
this.attachInteractions(svgElement);
} catch (e) {
console.error('Viz render error', e);
}
}
attachInteractions(svgElement: SVGSVGElement) {
const svg = d3.select(svgElement);
const self = this;
svg.selectAll('g.node')
.style('cursor', 'pointer')
.on('click', function (event) {
event.stopPropagation();
const id = d3.select(this).attr('id');
self.createChildNode(id);
self.render();
})
.on('contextmenu', function (event) {
event.preventDefault();
event.stopPropagation();
const id = d3.select(this).attr('id');
const { clientX: x, clientY: y } = event;
self.contextMenu.setContextMenu({ x, y, nodeId: id });
});
}
createChildNode(parentId: string) {
const id = crypto.randomUUID();
this.setGraph(prev => ({ ...prev, nodes: [...prev.nodes, { id, label: 'New node' }], edges: [...prev.edges, { from: parentId, to: id }] }));
}
}
function defaultGraph(): Graph {
return {
nodes: [
{ id: 'A', label: 'A' },
{ id: 'B', label: 'B' },
{ id: 'C', label: 'C' }
],
edges: [
{ from: 'A', to: 'B' },
{ from: 'B', to: 'C' }
]
};
}
export class Graph {
nodes: Node[] = [];
edges: Edge[] = [];
}
export class Edge {
from: string;
to: string;
constructor(from: string, to: string) {
this.from = from;
this.to = to;
}
}
export class Node {
public id: string;
public label?: string;
constructor(id: string) {
this.id = id;
}
}