d3.js apply rect collision in force layout - javascript
There are a couple of questions regarding collision detection in force layout but with none I could solve the problem.
The closest is: rect collision detection d3js
In the code below I do have circles but I treat them as rectangles (as that is what I want to solve in the end - bounding box of circle plus label).
Here is the code:
var graph = {
"nodes":[
{"name":"name-1","rating":1,"id":2951},
{"name":"name-2","rating":2,"id":654654},
{"name":"3","rating":3,"id":6546544},
{"name":"4","rating":4,"id":68987978},
{"name":"5","rating":5,"id":9878933},
{"name":"6","rating":6,"id":6161},
{"name":"7","rating":7,"id":64654},
{"name":"8","rating":8,"id":354654},
{"name":"9","rating":9,"id":8494},
{"name":"10","rating":10,"id":6846874},
{"name":"11","rating":11,"id":5487},
{"name":"12","rating":12,"id":34},
{"name":"13","rating":13,"id":65465465},
{"name":"14","rating":14,"id":5443},
{"name":"15","rating":15,"id":313514},
{"name":"16","rating":16,"id":36543614},
{"name":"17","rating":17,"id":3434},
{"name":"18","rating":18,"id":97413},
{"name":"19","rating":19,"id":97414},
{"name":"27","rating":20,"id":9134371}
],
"links":[
{"source":0,"target":5,"value":6, "label":"publishedOn"},
{"source":2,"target":5,"value":6, "label":"publishedOn"},
{"source":7,"target":1,"value":4, "label":"containsKeyword"},
{"source":8,"target":10,"value":3, "label":"containsKeyword"},
{"source":7,"target":14,"value":4, "label":"publishedBy"},
{"source":8,"target":15,"value":6, "label":"publishedBy"},
{"source":9,"target":1,"value":6, "label":"depicts"},
{"source":10,"target":1,"value":6, "label":"depicts"},
{"source":7,"target":1,"value":6, "label":"manageWebsite"},
{"source":16,"target":2,"value":5, "label":"manageWebsite"},
{"source":5,"target":3,"value":6, "label":"manageWebsite"},
{"source":16,"target":4,"value":6, "label":"manageWebsite"},
{"source":12,"target":9,"value":2, "label":"postedOn"},
{"source":13,"target":1,"value":6, "label":"childOf"},
{"source":10,"target":8,"value":8, "label":"describes"},
{"source":8,"target":11,"value":6, "label":"containsKeyword"},
{"source":2,"target":5,"value":3, "label":"manageWebsite"}
]
}
var width = 900;
var height = 700;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-500)
.linkDistance(function(d) { return d.value * 10;})
.gravity(0.5)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g");
var rect = svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all");
var container = svg.append("g");
force.nodes(graph.nodes).links(graph.links).start();
var links = container.append("g")
.attr("class", "links")
.selectAll(".link")
.data(graph.links)
.enter().append("path")
.attr("class", "link")
.style("stroke-width", 2);
var nodes = container.append("g")
.attr("class", "nodes")
.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
var circle = nodes.append("circle")
.attr("r", function(d) { return d.weight * 2+ 12; })
.style("fill", function(d) {
return color(1/d.rating);
});
nodes.append("text")
.attr("x", function(d) { return d.weight * 2+ 12 + 4; })
.text(function(d) { return d.name });
force.on("tick", function() {
var xnodes = force.nodes();
var len = xnodes.length;
var q = d3.geom.quadtree(xnodes);
for (i = 0; i < len; i++) {
//does not work
//q.visit(collideNew2(xnodes[i]));
}
links.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
});
nodes.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
});
function collideNew2(node) {
var nx1, nx2, ny1, ny2, size;
size = 250;
nx1 = node.x;
nx2 = nx1 + size;
ny1 = node.y;
ny2 = ny1 + size;
return function(quad, x1, y1, x2, y2) {
var dx, dy;
if (quad.point && (quad.point !== node)) {
if (overlap(node, quad.point)) {
dx = Math.min(nx2 - quad.point.x, quad.point.x2 - nx1) / 2;
node.x -= dx;
quad.point.x -= dx;
dy = Math.min(ny2 - quad.point.y, quad.point.y2 - ny1) / 2;
node.y -= dy;
quad.point.y += dy;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
};
};
function overlap(n, p) {
var nx1, nx2, ny1, ny2, size;
size = 250;
nx1 = n.x;
nx2 = nx1 + size;
ny1 = n.y;
ny2 = ny1 + size;
var x1 = p.x;
var x2 = x1 + size;
var y1 = p.y;
var y2 = y1 + size;
if (((x1 > nx1 && x1 < nx2) || (x2 > nx1 && x2 < nx2)) && ((y1 > ny1 && y1 < ny2) || (y2 > ny1 && y2 < ny2))) {
return true;
}
return false;
}
It seems that the overlap functions as well as the selected "nodes" is wrong. How can I fix this?
Related
D3.js static force layout not working with path?
Im trying change this example https://bl.ocks.org/mbostock/1667139 to use path instead of line, but its not working. Im try use own tick function like this: function tick() { link.attr("d", function(d) { var x1 = d.source.x, y1 = d.source.y, x2 = d.target.x, y2 = d.target.y, dx = x2 - x1, dy = y2 - y1, dr = Math.sqrt(dx * dx + dy * dy), // z uzla do ineho uzla drx = dr, dry = dr, xRotation = 0, largeArc = 0, sweep = 1; //do sameho seba if ( x1 === x2 && y1 === y2 ) { xRotation = -45; largeArc = 1; drx = 30; dry = 30; x2 = x2 + 1; y2 = y2 + 1; } return "M" + x1 + "," + y1 + "A" + drx + "," + dry + " " + xRotation + "," + largeArc + "," + sweep + " " + x2 + "," + y2; }); } I dont know, if i missing something or static force layout just cant use path. Force layout with path working normaly
From the docs (bolding mine): simulation.tick() Increments the current alpha by (alphaTarget - alpha) × alphaDecay; then invokes each registered force, passing the new alpha; then decrements each node’s velocity by velocity × velocityDecay; lastly increments each node’s position by velocity. This method does not dispatch events; events are only dispatched by the internal timer when the simulation is started automatically upon creation or by calling simulation.restart. The natural number of ticks when the simulation is started is ⌈log(alphaMin) / log(1 - alphaDecay)⌉; by default, this is 300. This method can be used in conjunction with simulation.stop to compute a static force layout. For large graphs, static layouts should be computed in a web worker to avoid freezing the user interface. Since it doesn't dispatch events your tick function is never called or used. Instead, just replace the line and set your path up once: <!DOCTYPE html> <svg width="960" height="500"></svg> <script src="https://d3js.org/d3.v4.min.js"></script> <script> var svg = d3.select("svg"), width = +svg.attr("width"), height = +svg.attr("height"), g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); var n = 100, nodes = d3.range(n).map(function(i) { return { index: i }; }), links = d3.range(n).map(function(i) { return { source: i, target: (i + 3) % n }; }); var simulation = d3.forceSimulation(nodes) .force("charge", d3.forceManyBody().strength(-80)) .force("link", d3.forceLink(links).distance(20).strength(1).iterations(10)) .force("x", d3.forceX()) .force("y", d3.forceY()) .stop(); var loading = svg.append("text") .attr("dy", "0.35em") .attr("text-anchor", "middle") .attr("font-family", "sans-serif") .attr("font-size", 10) .text("Simulating. One moment please…"); // Use a timeout to allow the rest of the page to load first. d3.timeout(function() { loading.remove(); // See https://github.com/d3/d3-force/blob/master/README.md#simulation_tick for (var i = 0, n = Math.ceil(Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay())); i < n; ++i) { simulation.tick(); } g.append("g") .attr("stroke", "#000") .attr("stroke-width", 1.5) .selectAll("line") .data(links) .enter().append("path") .attr("d", function(d) { var x1 = d.source.x, y1 = d.source.y, x2 = d.target.x, y2 = d.target.y, dx = x2 - x1, dy = y2 - y1, dr = Math.sqrt(dx * dx + dy * dy), // z uzla do ineho uzla drx = dr, dry = dr, xRotation = 0, largeArc = 0, sweep = 1; //do sameho seba if (x1 === x2 && y1 === y2) { xRotation = -45; largeArc = 1; drx = 30; dry = 30; x2 = x2 + 1; y2 = y2 + 1; } return "M" + x1 + "," + y1 + "A" + drx + "," + dry + " " + xRotation + "," + largeArc + "," + sweep + " " + x2 + "," + y2; }); g.append("g") .attr("stroke", "#fff") .attr("stroke-width", 1.5) .selectAll("circle") .data(nodes) .enter().append("circle") .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }) .attr("r", 4.5); }); </script> Response to comments: To append a circle and text as a "node", I would create a g, position that and then put the circle and text in it: var g = node .selectAll(".node") .data(nodes) .enter() .append("g") .attr("transform", function(d){ return "translate(" + d.x + "," + d.y + ")"; }); g.append("circle") .attr("class", "node") .attr("stroke", "#fff") .attr("r", 28); g.append("text") .text("test");
d3 js triangle in svg path
i must create 30 triangles that move away from current mouse position. i try with this code: var body = d3.select("body"); var mouse = []; var width = 1000; var height = 600; var numberOfTriangles = 30; var isMouseMoving = false; var triangle = d3.svg.symbolType["triangle-up"] function drawTriangles(number) { for (var i = 0; i < number; i++) { var dim = Math.random() * 400; svg.append("path") .attr("d", triangle.size(dim)) .attr("transform", function(d) { return "translate(" + Math.random() * width + "," + Math.random() * height + ")"; }) .attr("fill", "rgb(" + parseInt(Math.random() * 255) + "," + parseInt(Math.random() * 255) + "," + parseInt(Math.random() * 255) + ")") .attr("opacity", 2) .attr("class", "path" + i); } } function moveMouse() { if (isMouseMoving) { svg.selectAll('path').each(function(d, i) { var self = d3.select(this); self.attr('transform', function() { return "translate(" + mouse[0] + "," + mouse[1] + ")"; }) }) } } var svg = body.append("svg") .attr("width", width) .attr("height", height) .style("border", "1px solid black") .on("mousemove", function() { mouse = d3.mouse(this); isMouseMoving = true; }); drawTriangles(numberOfTriangles); d3.timer(function() { moveMouse() }); but i have this error: "Uncaught TypeError: Cannot read property 'size' of undefined at drawTriangles". Can someone help me? Thanks.
Your error is because of: var triangle = d3.svg.symbolType["triangle-up"]; If you fix the typo on symbolTypes, this returns undefined. d3.svg.symbolTypes simply returns an array of available symbols, it is not a mechanism to create a new symbol path generator. That said, what you really wanted is: var triangle = d3.svg.symbol().type("triangle-up"); This creates a proper triangle symbol generator. Taking this a little further, I'm not sure what you mean by that move away from current mouse position Your code does the exact opposite and puts all the triangles on the mouse cursor... EDITS <!DOCTYPE html> <meta charset="utf-8"> <body> <script src="//d3js.org/d3.v3.min.js"></script> <script> var width = 300, height = 300; var nodes = d3.range(200).map(function() { return {radius: Math.random() * 12 + 4}; }), root = nodes[0], color = d3.scale.category10(); root.radius = 0; root.fixed = true; var force = d3.layout.force() .gravity(0.05) .charge(function(d, i) { return i ? 0 : -1000; }) .nodes(nodes) .size([width, height]); force.start(); var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height) .style("border", "1px solid black") .style("margin","20px"); var triangle = d3.svg.symbol().type("triangle-up"); svg.selectAll("path") .data(nodes.slice(1)) .enter().append("path") .attr("d", function(d) { triangle.size(d.radius); return triangle(); }) .style("fill", function(d, i) { return color(i % 3); }); force.on("tick", function(e) { var q = d3.geom.quadtree(nodes), i = 0, n = nodes.length; while (++i < n) q.visit(collide(nodes[i])); svg.selectAll("path") .attr("transform", function(d){ return "translate(" + d.x + "," + d.y + ")"; }); }); svg.on("mousemove", function() { var p1 = d3.mouse(this); root.px = p1[0]; root.py = p1[1]; force.resume(); }); function collide(node) { var r = node.radius + 16, nx1 = node.x - r, nx2 = node.x + r, ny1 = node.y - r, ny2 = node.y + r; return function(quad, x1, y1, x2, y2) { if (quad.point && (quad.point !== node)) { var x = node.x - quad.point.x, y = node.y - quad.point.y, l = Math.sqrt(x * x + y * y), r = node.radius + quad.point.radius; if (l < r) { l = (l - r) / l * .5; node.x -= x *= l; node.y -= y *= l; quad.point.x += x; quad.point.y += y; } if (node.x > width) node.x = width; if (node.x < 0) node.x = 0; if (node.y > height) node.y = height; if (node.y < 0) node.y = 0; } return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; }; } </script>
Updating data in a Clustered Force Layout D3 bubble Chart
Morning ! I'm working on a bubble chart in D3.js, I'm quite close of what I need, but I've got a behavior problem when updating data (changing set). I would like the bubbles to stay in place, and update size and position without the bouncy effect due to colision :( function convertData(m){ var nodes = m; nodes.forEach(function(d) { clusters[d.cluster] = d; d.radius = Math.ceil(Math.sqrt(d.pop/Math.PI))*10; }); return nodes; } function updateAnnee(annee){ nodes = convertData(annee); var svg = d3.select("svg") var node = svg.selectAll("circle") .data(nodes) node.transition() .duration(0) .attr('r', function(d){ return d.radius}); node .enter().append("circle") node .exit().remove(); var force = d3.layout.force() .nodes(nodes) .size([width, height]) .gravity(.02) .charge(0) .on("tick", tick) .start(); function tick(e) { node .each(cluster(10 * e.alpha * e.alpha)) .each(collide(.5)) .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y-50; }) } // Move d to be adjacent to the cluster node. function cluster(alpha) { return function(d) { var cluster = clusters[d.cluster]; if (cluster === d) return; var x = d.x - cluster.x, y = d.y - cluster.y, l = Math.sqrt(x * x + y * y), r = d.radius + cluster.radius; if (l != r) { l = (l - r) / l * alpha; d.x -= x *= l; d.y -= y *= l; cluster.x += x; cluster.y += y; } }; } // Resolves collisions between d and all other circles. function collide(alpha) { var quadtree = d3.geom.quadtree(nodes); return function(d) { var r = d.radius + maxRadius + Math.max(padding, clusterPadding), nx1 = d.x - r, nx2 = d.x + r, ny1 = d.y - r, ny2 = d.y + r; quadtree.visit(function(quad, x1, y1, x2, y2) { if (quad.point && (quad.point !== d)) { var x = d.x - quad.point.x, y = d.y - quad.point.y, l = Math.sqrt(x * x + y * y), r = d.radius + quad.point.radius + (d.cluster === quad.point.cluster ? padding : clusterPadding); if (l < r) { l = (l - r) / l * alpha; d.x -= x *= l; d.y -= y *= l; quad.point.x += x; quad.point.y += y; } } return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; }); }; } } What I have so far is viewable here : https://jsfiddle.net/hf998do7/1/ This is based on the work of Mr Bostock http://bl.ocks.org/mbostock/7881887 Thanks
How can I force surrounding elements to move in response to a change in size of one of the elements?
I found some code that displays a group of circles. I slightly modified the code to allow the user to click on any of the circles to change its radius. The problem is that that enlarged circle overlaps the other other circle. I want the other circles to move so that the larger circle doesn't overlap any of the adjacent circles. Here is the code I have: <!DOCTYPE html> <meta charset="utf-8"> <body> <script src="http://d3js.org/d3.v3.min.js"></script> <script> var width = 960, height = 500; var nodes = d3.range(200).map(function() { return {radius: Math.random() * 12 + 4}; }), root = nodes[0], color = d3.scale.category10(); root.radius = 0; root.fixed = true; var force = d3.layout.force() .gravity(0.05) .charge(function(d, i) { return i ? 0 : -2000; }) .nodes(nodes) .size([width, height]); force.start(); var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height); svg.selectAll("circle") .data(nodes.slice(1)) .enter().append("circle") .attr("r", function(d) { return d.radius; }) .style("fill", function(d, i) { return color(i % 3); }) .on("click", function (d) { d3.select(this).attr("r", 30); force.start(); }); force.on("tick", function(e) { var q = d3.geom.quadtree(nodes), i = 0, n = nodes.length; while (++i < n) q.visit(collide(nodes[i])); svg.selectAll("circle") .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); }); function collide(node) { var r = node.radius + 16, nx1 = node.x - r, nx2 = node.x + r, ny1 = node.y - r, ny2 = node.y + r; return function(quad, x1, y1, x2, y2) { if (quad.point && (quad.point !== node)) { var x = node.x - quad.point.x, y = node.y - quad.point.y, l = Math.sqrt(x * x + y * y), r = node.radius + quad.point.radius; if (l < r) { l = (l - r) / l * .5; node.x -= x *= l; node.y -= y *= l; quad.point.x += x; quad.point.y += y; } } return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; }; } </script>
d3.js collision detection with quadtree
I'm trying to draw a graph with d3.js that implements collisions between nodes. I try the following codigo.Pero not work. If I remove the collision detection draw but overlapping nodes ... <html> <head> <title>Demo</title> <script type="text/javascript" src="../d3.v2.js"></script> <script type="text/javascript" src="jquery-1.8.1.js"></script> <style type="text/css"> </style> </head> <body> <script type="text/javascript"> window.onload = function() { print(); } function print(){ var w = 960; var h = 500; var r = 20; var fill = d3.scale.category20(); var force = d3.layout.force() .gravity(.01) .charge(-120) .linkDistance(30) .size([w, h]); var svg = d3.select("body") .append("svg") .attr("width", w) .attr("height", h); function() { d3.json("datos.json", function(json) { var nodes = svg.selectAll("circle") .data(json.nodes) .enter() .append("circle") .attr("r", r - .75) .style("fill", function(d) {return "#FF7F0E"}) .style("stroke", function(d) { return "green"; }) .style("stroke-width", function(d) { return "13px"; }) .call(force.drag); force.nodes(json.nodes) .on("tick", tick) .start(); var text = svg.append("g").selectAll("g") .data(force.nodes()) .enter() .append("g") .style("font-size", function(d) { return "12px"; }) .style("fill", function(d) { return "red"; }) .style("font-weight", function(d) { return "bold"; }); text.append("text") .attr("text-anchor", "middle") .attr("y", ".31em") .text(function(d) { return d.name; }); function tick(e) { nodes.each(collide(.5)) .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); text.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")";}); } function collide(alpha) { var quadtree = d3.geom.quadtree(nodes); return function(d) { var ratio = r, nx1 = d.x - ratio, nx2 = d.x + ratio, ny1 = d.y - ratio, ny2 = d.y + ratio; quadtree.visit(function(quad, x1, y1, x2, y2) { if (quad.point && (quad.point !== d)) { var x = d.x - quad.point.x, y = d.y - quad.point.y, l = Math.sqrt(x * x + y * y), r = d.radius + quad.point.radius + (d.color !== quad.point.color) * padding; if (l < r) { l = (l - r) / l * alpha; d.x -= x *= l; d.y -= y *= l; quad.point.x += x; quad.point.y += y; } } return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; }); }; } }); })(); } </script> </body> </html> If I remove the collision detection works but overlapping nodes. I need help :(