119 lines
4.1 KiB
JavaScript
119 lines
4.1 KiB
JavaScript
// ─── Configuration ────────────────────────────────────────────
|
|
// Each pair is [sourceId, targetId].
|
|
// Arrows go from the bottom-center of the source
|
|
// to the top-center of the target.
|
|
const CONNECTIONS = [
|
|
['proj', 'fich'],
|
|
['proj', 'hack'],
|
|
['fich', 'rust'],
|
|
['rust', 'alac'],
|
|
['hack', 'alac'],
|
|
['alac', 'mine'],
|
|
];
|
|
|
|
// ─── Setup ────────────────────────────────────────────────────
|
|
const scene = document.getElementById('body');
|
|
const svg = document.getElementById('arrows');
|
|
|
|
// Create one SVG <path> per connection
|
|
const pathEls = CONNECTIONS.map(() => {
|
|
const p = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
p.setAttribute('class', 'arrow-path');
|
|
p.setAttribute('marker-end', 'url(#head)');
|
|
svg.appendChild(p);
|
|
return p;
|
|
});
|
|
|
|
// ─── Core: Manhattan path between two elements ────────────────
|
|
// r1 = bounding rect of source, r2 = bounding rect of target
|
|
// Both rects are relative to the scene container.
|
|
function manhattanPath(r1, r2) {
|
|
let x1 = r1.left + r1.width / 2; // bottom-center of source
|
|
const y1 = r1.top + r1.height + 10;
|
|
let x2 = r2.left + r2.width / 2; // top-center of target
|
|
const y2 = r2.top + 5; // stop 8px above target (arrowhead gap)
|
|
|
|
let shift = 5;
|
|
if (x1 > x2) {
|
|
x1 -= shift;
|
|
x2 += shift;
|
|
} else if (x2 > x1) {
|
|
x1 += shift;
|
|
x2 -= shift;
|
|
}
|
|
|
|
const ymid = y1 + (y2 - y1) / 2; // elbow halfway between the two
|
|
|
|
// M: move to source bottom
|
|
// L: go straight down to mid-y
|
|
// L: go horizontally to target x
|
|
// L: go straight down to target top
|
|
return `M ${x1} ${y1}
|
|
L ${x1} ${ymid}
|
|
L ${x2} ${ymid}
|
|
L ${x2} ${y2}`;
|
|
}
|
|
|
|
// ─── Redraw all arrows ────────────────────────────────────────
|
|
function redraw() {
|
|
const sceneRect = scene.getBoundingClientRect();
|
|
|
|
CONNECTIONS.forEach(([srcId, tgtId], i) => {
|
|
const src = document.getElementById(srcId).getBoundingClientRect();
|
|
const tgt = document.getElementById(tgtId).getBoundingClientRect();
|
|
|
|
// Convert from viewport coords → scene-local coords
|
|
const r1 = {
|
|
left: src.left - sceneRect.left,
|
|
top: src.top - sceneRect.top,
|
|
width: src.width,
|
|
height: src.height,
|
|
};
|
|
const r2 = {
|
|
left: tgt.left - sceneRect.left,
|
|
top: tgt.top - sceneRect.top,
|
|
width: tgt.width,
|
|
height: tgt.height,
|
|
};
|
|
|
|
pathEls[i].setAttribute('d', manhattanPath(r1, r2));
|
|
if (tgt.left > src.left) {
|
|
pathEls[i].style.stroke = "#008000";
|
|
} else if (tgt.left < src.left){
|
|
pathEls[i].style.stroke = "#ff0000";
|
|
} else {
|
|
pathEls[i].style.stroke = "#0000ff";
|
|
}
|
|
});
|
|
}
|
|
|
|
// ─── Drag logic ───────────────────────────────────────────────
|
|
document.querySelectorAll('.node').forEach(node => {
|
|
node.addEventListener('mousedown', e => {
|
|
e.preventDefault();
|
|
const startX = e.clientX;
|
|
const startY = e.clientY;
|
|
const originL = parseInt(node.style.left);
|
|
const originT = parseInt(node.style.top);
|
|
|
|
const onMove = e => {
|
|
node.style.left = Math.max(0, originL + e.clientX - startX) + 'px';
|
|
node.style.top = Math.max(0, originT + e.clientY - startY) + 'px';
|
|
redraw();
|
|
};
|
|
const onUp = () => {
|
|
window.removeEventListener('mousemove', onMove);
|
|
window.removeEventListener('mouseup', onUp);
|
|
};
|
|
|
|
window.addEventListener('mousemove', onMove);
|
|
window.addEventListener('mouseup', onUp);
|
|
});
|
|
});
|
|
|
|
// ─── Initial draw ─────────────────────────────────────────────
|
|
window.addEventListener('load', redraw);
|
|
|
|
// also redraws if anything inside the scene changes size
|
|
new ResizeObserver(redraw).observe(scene);
|