sage/wwwroot/tmp/dag1.html
2025-11-03 15:34:22 +08:00

166 lines
4.2 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DAG 可视化编辑器</title>
<style>
body { font-family: Arial; }
#canvas { border: 1px solid #ccc; width: 100%; height: 600px; position: relative; }
.node {
position: absolute;
padding: 10px 20px;
border: 2px solid #333;
border-radius: 8px;
background: #f9f9f9;
cursor: move;
user-select: none;
}
svg {
position: absolute;
top: 0; left: 0; width: 100%; height: 100%;
pointer-events: none;
}
line, path {
stroke: #333;
stroke-width: 2;
fill: none;
marker-end: url(#arrow);
pointer-events: auto;
}
</style>
</head>
<body>
<h3>DAG 可视化编辑器</h3>
<div id="canvas">
<svg id="edges"></svg>
</div>
<script>
let nodes = [];
let edges = [];
let selectedNode = null;
const canvas = document.getElementById("canvas");
const svg = document.getElementById("edges");
function createNode(id, text, x, y) {
const div = document.createElement("div");
div.className = "node";
div.innerText = text;
div.contentEditable = true;
div.style.left = x + "px";
div.style.top = y + "px";
div.dataset.id = id;
div.onmousedown = (e) => startDrag(e, div);
div.onclick = (e) => selectNode(e, div);
canvas.appendChild(div);
nodes.push({id, x, y, el: div});
}
function drawEdges() {
svg.innerHTML = `
<defs>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="10" refY="3" orient="auto">
<path d="M0,0 L0,6 L9,3 z" fill="#333" />
</marker>
</defs>`;
edges.forEach(edge => {
const from = nodes.find(n => n.id === edge.from);
const to = nodes.find(n => n.id === edge.to);
if (from && to) {
const x1 = from.x + from.el.offsetWidth/2;
const y1 = from.y + from.el.offsetHeight/2;
const x2 = to.x + to.el.offsetWidth/2;
const y2 = to.y + to.el.offsetHeight/2;
const line = document.createElementNS("http://www.w3.org/2000/svg", "line");
line.setAttribute("x1", x1);
line.setAttribute("y1", y1);
line.setAttribute("x2", x2);
line.setAttribute("y2", y2);
line.setAttribute("stroke", "black");
line.setAttribute("marker-end", "url(#arrow)");
svg.appendChild(line);
}
});
}
function selectNode(e, div) {
if (!selectedNode) {
selectedNode = div.dataset.id;
} else {
if (selectedNode !== div.dataset.id) {
edges.push({from: selectedNode, to: div.dataset.id});
drawEdges();
}
selectedNode = null;
}
e.stopPropagation();
}
let dragNode = null, offsetX = 0, offsetY = 0;
function startDrag(e, div) {
dragNode = div;
offsetX = e.offsetX;
offsetY = e.offsetY;
document.onmousemove = drag;
document.onmouseup = stopDrag;
}
function drag(e) {
if (dragNode) {
dragNode.style.left = (e.pageX - canvas.offsetLeft - offsetX) + "px";
dragNode.style.top = (e.pageY - canvas.offsetTop - offsetY) + "px";
let n = nodes.find(n => n.id === dragNode.dataset.id);
n.x = e.pageX - canvas.offsetLeft - offsetX;
n.y = e.pageY - canvas.offsetTop - offsetY;
drawEdges();
}
}
function stopDrag() {
dragNode = null;
document.onmousemove = null;
document.onmouseup = null;
}
// 简单自动布局:拓扑排序分层排布
function autoLayout() {
let layerMap = {};
let visited = new Set();
function dfs(nodeId, depth) {
if (layerMap[nodeId] === undefined || depth > layerMap[nodeId]) {
layerMap[nodeId] = depth;
}
edges.filter(e => e.from === nodeId).forEach(e => dfs(e.to, depth+1));
}
nodes.forEach(n => dfs(n.id, 0));
let maxLayer = Math.max(...Object.values(layerMap));
let layerGroups = Array.from({length: maxLayer+1}, () => []);
for (let n of nodes) {
let layer = layerMap[n.id] || 0;
layerGroups[layer].push(n);
}
layerGroups.forEach((group, depth) => {
group.forEach((n, i) => {
n.x = 150 * i + 50;
n.y = depth * 100 + 50;
n.el.style.left = n.x + "px";
n.el.style.top = n.y + "px";
});
});
drawEdges();
}
// 测试数据
createNode("1", "开始", 50, 50);
createNode("2", "处理", 250, 200);
createNode("3", "结束", 450, 350);
edges.push({from: "1", to: "2"});
edges.push({from: "2", to: "3"});
drawEdges();
autoLayout();
</script>
</body>
</html>