const w = 400, h = 400; const svg = SVG("#svg").size(w, h); const eGroup = svg.group(); const vGroup = svg.group(); let startX, startY, lastX, lastY, isDown = false, curSelected; class Vertex { constructor(name) { this.obj = vGroup .circle(40) .center(Math.random() * w / 2 + w / 4, Math.random() * h / 2 + h / 4) .fill("white") .stroke({width: 2, color: "black"}) .mousedown((e) => { lastX = e.clientX; lastY = e.clientY; startX = e.clientX; startY = e.clientY; curSelected = name; isDown = true; this.speed = {x: 0, y: 0}; }); this.textObj = vGroup .text(name) .x(this.obj.cx()) .cy(this.obj.cy()) .font({ family: "monospace", size: 20 }) .attr({ "text-anchor": "middle", "pointer-events": "none" }); this.name = name; this.speed = {x: 0, y: 0}; this.fixed = false; } dmove(x, y) { this.obj.dmove(x, y); this.textObj.dmove(x, y); } remove() { this.obj.remove(); this.textObj.remove(); } } // class Edge { // constructor(name, from, to) { // this.obj = eGroup.line().stroke({width: 2, color: "black"}); // this.textObj = vGroup // .text(name) // .x(this.obj.cx()) // .cy(this.obj.cy()) // .font({ family: "monospace", size: 20 }) // .attr({ "text-anchor": "middle", "pointer-events": "none" }); // this.from = from; // this.to = to; // this.name = name; // } // } const vertices = new Map(); const edges = new Map(); function updateFromTextArea() { const text = textarea.value; const curVertices = new Set(); const curEdges = new Set(); for (let line of text.split('\n')) { if (line.length === 0) continue; line = line.trim(); let tokens = line.split(' '); if (tokens.length === 1 && tokens[0] !== "") { let i = tokens[0]; curVertices.add(i); } else if (tokens.length === 2) { let [i, j] = tokens; curVertices.add(i); curVertices.add(j); if (i !== j) { curEdges.add(line); } } } for (let prevVertex of vertices.keys()) { if (!curVertices.has(prevVertex)) { vertices.get(prevVertex).remove(); vertices.delete(prevVertex); } } for (let newVertex of curVertices) { if (!vertices.has(newVertex)) { vertices.set(newVertex, new Vertex(newVertex)); } } for (let edgeText of edges.keys()) { const [u, v] = edgeText.split(' '); if (!curEdges.has(u + ' ' + v) && !curEdges.has(v + ' ' + u)) { edges.get(edgeText).remove(); edges.delete(edgeText); } } for (let edgeText of curEdges) { const [u, v] = edgeText.split(' '); if (!edges.has(u + ' ' + v) && !edges.has(v + ' ' + u)) { edges.set(edgeText, eGroup.line().stroke({width: 2, color: "black"})); } } } const textarea = document.getElementById("input"); textarea.oninput = updateFromTextArea; window.onload = updateFromTextArea; svg.mousemove((e) => { if (isDown) { vertices.get(curSelected).dmove(e.clientX - lastX, e.clientY - lastY); lastX = e.clientX; lastY = e.clientY; } }).mouseup((e) => { if (startX === e.clientX && startY === e.clientY) { if (!vertices.get(curSelected).fixed) { vertices.get(curSelected).obj.stroke({ width: 4 }); } else { vertices.get(curSelected).obj.stroke({ width: 2 }); } vertices.get(curSelected).fixed ^= true; } isDown = false; curSelected = undefined; }); const idealLength = 150; let lastStamp; function step(timeStamp) { if (lastStamp === undefined || (timeStamp - lastStamp) / 1000 > 0.03) { lastStamp = timeStamp; } const dt = (timeStamp - lastStamp) / 1000; for (let edgeText of edges.keys()) { const [i, j] = edgeText.split(' '); const u = vertices.get(i).obj; const v = vertices.get(j).obj; const length = Math.hypot(u.cx() - v.cx(), u.cy() - v.cy()); const xf = (v.cx() - u.cx()) / length * (length - idealLength); const yf = (v.cy() - u.cy()) / length * (length - idealLength); vertices.get(i).speed.x += xf; vertices.get(i).speed.y += yf; vertices.get(j).speed.x -= xf; vertices.get(j).speed.y -= yf; } for (let i of vertices.keys()) { const u = vertices.get(i).obj; for (let j of vertices.keys()) { if (i === j) continue; const v = vertices.get(j).obj; const length = Math.hypot(u.cx() - v.cx(), u.cy() - v.cy()); let xf, yf; if (length < idealLength) { xf = (u.cx() - v.cx()) / Math.pow(length, 3) * 7500; yf = (u.cy() - v.cy()) / Math.pow(length, 3) * 7500; } else { xf = (v.cx() - u.cx()) / Math.pow(length, 3) * 2500; yf = (v.cy() - u.cy()) / Math.pow(length, 3) * 2500; } vertices.get(i).speed.x += xf; vertices.get(i).speed.y += yf; } } for (let i of vertices.keys()) { const v = vertices.get(i); v.speed.x *= Math.pow(0.001, dt); v.speed.y *= Math.pow(0.001, dt); if (v.name !== curSelected && !v.fixed) { v.dmove(dt * v.speed.x, dt * v.speed.y); } } for (let edgeText of edges.keys()) { const [i, j] = edgeText.split(' '); const u = vertices.get(i).obj; const v = vertices.get(j).obj; edges.get(edgeText).plot(u.cx(), u.cy(), v.cx(), v.cy()); } for (let i of vertices.keys()) { const v = vertices.get(i).obj; v.dmove(0, 0); } lastStamp = timeStamp; window.requestAnimationFrame(step); } window.requestAnimationFrame(step);