diff --git a/index.html b/index.html new file mode 100644 index 0000000..5ef1704 --- /dev/null +++ b/index.html @@ -0,0 +1,28 @@ + + + + + + + Document + + + + + + + + + + + \ No newline at end of file diff --git a/script.js b/script.js new file mode 100644 index 0000000..fa7387c --- /dev/null +++ b/script.js @@ -0,0 +1,195 @@ +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))