196 lines
4.9 KiB
JavaScript
196 lines
4.9 KiB
JavaScript
const SVG_WIDTH = 960;
|
|
const SVG_HEIGHT = 500;
|
|
|
|
const BOX_WIDTH = 60;
|
|
const BOX_HEIGHT = 20;
|
|
|
|
const graph = {
|
|
nodes: [
|
|
{
|
|
name: "from",
|
|
fixed: true,
|
|
x: 0,
|
|
y: 0,
|
|
w: BOX_WIDTH,
|
|
h: BOX_HEIGHT,
|
|
},
|
|
{
|
|
name: 'to',
|
|
fixed: true,
|
|
x: 250,
|
|
y: 250,
|
|
w: BOX_WIDTH,
|
|
h: BOX_HEIGHT,
|
|
}
|
|
],
|
|
links: [
|
|
{
|
|
// id: 0,
|
|
source: 0,
|
|
target: 1,
|
|
}
|
|
]
|
|
}
|
|
|
|
const generateMarkerId = link => `arrowhead-${link.source.index}-${link.target.index}`.replace(/\s/g, "-");
|
|
|
|
const svg = d3
|
|
.select("body")
|
|
.append("svg")
|
|
.attr("width", SVG_WIDTH)
|
|
.attr("height", SVG_HEIGHT);
|
|
|
|
const forceLink = d3
|
|
.forceLink()
|
|
.id(link => link.id)
|
|
.strength(_ => 0.002);
|
|
|
|
const forceNode = d3
|
|
.forceManyBody()
|
|
.strength(-400);
|
|
|
|
const forceDragDrop = d3
|
|
.drag()
|
|
.on("start", node => {
|
|
node.fx = node.x;
|
|
node.fy = node.y;
|
|
})
|
|
.on("drag", (event, node) => {
|
|
force.alphaTarget(0.1).restart();
|
|
node.fx = event.x;
|
|
node.fy = event.y;
|
|
})
|
|
.on("end", (event, node) => {
|
|
if (!event.active) force.alphaTarget(0);
|
|
node.fx = null;
|
|
node.fy = null;
|
|
})
|
|
|
|
const force = d3
|
|
.forceSimulation()
|
|
.force("charge", forceNode)
|
|
.force("link", forceLink);
|
|
|
|
const link = svg
|
|
.selectAll(".link")
|
|
.data(graph.links)
|
|
.enter()
|
|
.append("line")
|
|
.attr("class", "link")
|
|
.style("stroke-width", "5")
|
|
.attr("marker-end", "url(#arrow)")
|
|
.each(link => {
|
|
svg
|
|
.append("marker")
|
|
.attr("id", generateMarkerId(link))
|
|
.attr("viewBox", "0 -5 10 5")
|
|
.attr("markerWidth", 2)
|
|
.attr("markerHeight", 4)
|
|
.attr("orient", "auto")
|
|
.attr("refX", 30)
|
|
.attr("refY", 0)
|
|
.append("path")
|
|
.attr("d", "M 0 5 L 10 0 L 0 -5 Z")
|
|
.attr("fill", link.color); // Set the fill color to the link's color
|
|
})
|
|
.attr("marker-end", link => `url(#${generateMarkerId(link)})`)
|
|
.call(forceDragDrop)
|
|
|
|
const node = svg
|
|
.selectAll(".node")
|
|
.data(graph.nodes)
|
|
.enter()
|
|
.append("rect")
|
|
.attr("class", "node")
|
|
.attr("width", d => d.w)
|
|
.attr("height", d => d.h)
|
|
.style("fill", "blue")
|
|
.call(forceDragDrop)
|
|
.append("title")
|
|
.text(d => d.name);
|
|
|
|
// force.start();
|
|
|
|
force.on("tick", () => {
|
|
console.log("lmao")
|
|
link
|
|
.attr("x1", d => d.source.x) // + d.source.w / 2
|
|
.attr("y1", d => d.source.y) // + d.source.h / 2
|
|
.attr("x2", d => d.target.x) // + d.target.w / 2
|
|
.attr("y2", d => d.target.y) // + d.target.h / 2
|
|
// .each(link => {
|
|
// const markerId = generateMarkerId(link);
|
|
|
|
// const linkAngle = angle_between(
|
|
// link.source.x,
|
|
// -link.source.y,
|
|
// link.target.x,
|
|
// -link.target.y
|
|
// );
|
|
|
|
// const intersectionPoint = box_intersection(
|
|
// linkAngle,
|
|
// link.target.w,
|
|
// link.target.h,
|
|
// 0
|
|
// );
|
|
|
|
// const x = d3.select(`#${markerId}`)
|
|
// const distance = Math.sqrt( ((intersectionPoint.x)**2) + ((intersectionPoint.y) ** 2) );
|
|
// //console.log(distance)
|
|
// x.attr('refX', _ => distance);
|
|
// });
|
|
|
|
node
|
|
.attr("x", d => d.x)
|
|
.attr("y", d => d.y);
|
|
});
|
|
|
|
const angle_between = (x1, y1, x2, y2) => {
|
|
let angle = Math.atan2(y2-y1, x2-x1);
|
|
if (angle < 0) angle += 2*Math.PI
|
|
return angle;
|
|
}
|
|
|
|
const mod = (n, m) => ((n % m) + m) % m;
|
|
|
|
const hits_border_radius = (angle, box_width, box_height, border_radius) => {
|
|
const height = box_height / 2;
|
|
const width = box_width / 2;
|
|
|
|
const p1 = [width, height - border_radius]
|
|
const p2 = [width - border_radius, height]
|
|
|
|
const border_angle1 = angle_between(0,0, p1[0], p1[1])
|
|
const border_angle2 = angle_between(0,0, p2[0], p2[1])
|
|
|
|
return [
|
|
angle,
|
|
mod(angle-(Math.PI/2), Math.PI*2),
|
|
mod(angle-(Math.PI), Math.PI*2),
|
|
mod(angle-(3*Math.PI/2), Math.PI*2)
|
|
].some(angle => border_angle1 < angle && angle < border_angle2);
|
|
}
|
|
|
|
const box_intersection = (angle, box_width, box_height, border_radius) => {
|
|
const half_height = box_height / 2;
|
|
const half_width = box_width / 2;
|
|
|
|
if (hits_border_radius(angle, half_width, half_height, border_radius)) {
|
|
return arc_border_box_intersection(angle, half_width, half_height, border_radius)
|
|
}
|
|
|
|
return straight_border_box_intersection(angle, half_width, half_height)
|
|
}
|
|
|
|
const straight_border_box_intersection = (angle, box_width, box_height) => {
|
|
return {y: Math.cos(angle)*box_width, x: Math.sin(angle)*box_height}
|
|
}
|
|
|
|
const arc_border_box_intersection = (angle, box_width, box_height, border_radius) => {
|
|
//TODO:
|
|
return 1;
|
|
}
|
|
|
|
// console.log(straight_border_box_intersection(angle_between(10,0,0,10), 10, 10))
|