D3: Show network reaching layout, then stop force - javascript

I'm trying to get my D3 network to freeze after it reaches a nice layout (alpha reaches 0). I want the force to stop completely, even when a node is dragged (the user should be able to rearrange the nodes manually). I think I know how to do the second part of this, by modifying the functions that are called on mousedown and mouseup for the nodes. However, I can't get the original layout and freezing to work.
I've looked at the examples for "static" force layouts, where the network is displayed only after the layout is completed. However, I want the network to display as it's reaching the stable layout. I added this to the end of the function that draws the network:
while (force.alpha() >0.005) {
force.tick();
}
force.stop();
With this addition, the network doesn't display until it gets to force.stop(). Does anyone know how I can get it to display while it's "ticking"?
EDIT: Here's my implementation of the tick function:
function tick(e) {
console.log(force.alpha());
if (force.alpha() <0.05) {
force.stop();
}
var h = svgH;
if (e.alpha < 0.05) {
var q = d3.geom.quadtree(nodes),
i = 0,
n = nodes.length;
while (++i < n) {
q.visit(collide(nodes[i], e.alpha));
}
}
path.attr("d", function(d) {
// Total difference in x and y from source to target
diffX = d.target.x - d.source.x;
diffY = d.target.y - d.source.y;
// Length of path from center of source node to center of target node
pathLength = Math.sqrt((diffX * diffX) + (diffY * diffY));
// x and y distances from center to outside edge of target node
offsetX = (diffX * d.target.radius) / pathLength;
offsetY = (diffY * d.target.radius) / pathLength;
if (d.target.y < d.source.y) {
var avgY = (d.target.y + d.source.y)/2;
if (d.target.fixed != true) {
d.target.y = avgY;
}
if (d.source.fixed != true) {
d.source.y = avgY;
}
}
return "M" + d.source.x + "," + d.source.y + "L" + (d.target.x - offsetX) + "," + (d.target.y - offsetY);
});
// Keep circles within bounds of screen
var r = 6;
circle.attr("cx", function(d) { return d.x = Math.max(r + d.radius, Math.min(w - r, d.x)); })
.attr("cy", function(d) {
return d.y = Math.max(d.radius, Math.min(h - d.radius, d.y));
});
text.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}

Check the stop condition in the tick event handler -- this way you can redraw the network on each tick and stop.

Related

Calling a function to feed an .attr() in d3

I have .attr that create my link paths in d3 as below, Lines 42 in the demo:
link.each(function (d){})
.attr('x1', function (d) {
return d.source.x;
})
.attr('y1', function (d) {
return d.source.y;
})
.attr('x2', function (d) {
return d.target.x;
})
.attr('y2', function (d) {
return d.target.y;
});
The above draws my path links. I've changed this to calculate making the link paths smaller. However, there is a lot of repeated logic and not sure how to call a function to return my the values required. As below:
link.attr("x1", function(d) {
// Total difference in x and y from source to target
let diffX = d.target.x - d.source.x;
let diffY = d.target.y - d.source.y;
// Length of path from center of source node to center of target node
let pathLength = Math.sqrt((diffX * diffX) + (diffY * diffY));
// x and y distances from center to outside edge of target node
let offsetX = (diffX * 40) / pathLength;
return d.source.x + offsetX;
}).attr("y1", function(d) {
let diffX = d.target.x - d.source.x;
let diffY = d.target.y - d.source.y;
let pathLength = Math.sqrt((diffX * diffX) + (diffY * diffY));
let offsetY = (diffY * 40) / pathLength;
return d.source.y + offsetY;
}).attr("x2", function(d) {
let diffX = d.target.x - d.source.x;
let diffY = d.target.y - d.source.y;
let pathLength = Math.sqrt((diffX * diffX) + (diffY * diffY));
let offsetX = (diffX * 40) / pathLength;
return d.target.x - offsetX;
}).attr("y2", function(d) {
let diffX = d.target.x - d.source.x;
let diffY = d.target.y - d.source.y;
let pathLength = Math.sqrt((diffX * diffX) + (diffY * diffY));
let offsetY = (diffY * 40) / pathLength;
return d.target.y - offsetY;
})
It returns offsetX for d.source.x/d.target.x values and offsetY for d.source.y/d.target.y.
Ideally i dont want all this repeated logic and have tried doing a .call() on the link which goes into a function but then I dont know how to return the result into the attributes themselves e.g.
.attr('x1', function (d) {
return d.source.x; //function return from .call()
})
I've also tried putting the function call within the above attribute but get an error of "is not a function" e.g
.attr('x1', function (d) {
return this.myNewPathFunction(d);
})
Lines 42 in the demo
Your objective is both writing less lines of repetitive code and reducing the amount of calculations needed.
One of the idiomatic D3 solutions is using selection.each();
Thus, in your case:
link.each(d, i, n) {
// Total difference in x and y from source to target
let diffX = d.target.x - d.source.x;
let diffY = d.target.y - d.source.y;
// Length of path from center of source node to center of target node
let pathLength = Math.sqrt(diffX * diffX + diffY * diffY);
// x and y distances from center to outside edge of target node
let offsetX = (diffX * 40) / pathLength;
let offsetY = (diffY * 40) / pathLength;
d3.select(n[i])
.attr("x1", d.source.x + offsetX)
.attr("y1", d.source.y + offsetY)
.attr("x2", d.target.x - offsetX)
.attr("y2", d.target.y - offsetY);
};
Note that the computation of offsetX and offsetY happens only once per element. In your specific case it's not a heavy computation, but when it is it's a good idea reducing the burden on the browser.
You could move your logic to a function like so:
const coordinate = (position, d, index, nodes) => {
// Total difference in x and y from source to target
let diffX = d.target.x - d.source.x;
let diffY = d.target.y - d.source.y;
// Length of path from center of source node to center of target node
let pathLength = Math.sqrt(diffX * diffX + diffY * diffY);
// x and y distances from center to outside edge of target node
let offsetX = (diffX * 40) / pathLength;
let offsetY = (diffY * 40) / pathLength;
if (position === 'x1') return d.source.x + offsetX;
if (position === 'y1') return d.source.y + offsetY;
if (position === 'x2') return d.target.x - offsetX;
if (position === 'y2') return d.target.y - offsetY;
};
link
.attr('x1', (d, i, nodes) => coordinate('x1', d, i, nodes))
.attr('y1', (d, i, nodes) => coordinate('y1', d, i, nodes))
.attr('x2', (d, i, nodes) => coordinate('x2', d, i, nodes))
.attr('y2', (d, i, nodes) => coordinate('y2', d, i, nodes));

How to change the color of two circles upon overlap?

Hello I would like to know how it would be possible two make it that two circles change color when they overlap. Preferably the section that is overlapped would become white since its meant to represent sets.
var canvas = d3.select("canvas"),
context = canvas.node().getContext("2d"),
width = canvas.property("width"),
height = canvas.property("height"),
radius = 32;
var circles = d3.range(4).map(function(i) {
return {
index: i,
x: Math.round(Math.random() * (width - radius * 2) + radius),
y: Math.round(Math.random() * (height - radius * 2) + radius)
};
});
var color = d3.scaleOrdinal()
.range(d3.schemeCategory20);
render();
canvas.call(d3.drag()
.subject(dragsubject)
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
.on("start.render drag.render end.render", render));
function render() {
context.clearRect(0, 0, width, height);
for (var i = 0, n = circles.length, circle; i < n; ++i) {
circle = circles[i];
context.beginPath();
context.moveTo(circle.x + radius, circle.y);
context.arc(circle.x, circle.y, radius, 0, 2 * Math.PI);
context.fillStyle = color(circle.index);
context.fill();
if (circle.active) {
context.lineWidth = 2;
context.stroke();
}
}
}
function dragsubject() {
for (var i = circles.length - 1, circle, x, y; i >= 0; --i) {
circle = circles[i];
x = circle.x - d3.event.x;
y = circle.y - d3.event.y;
if (x * x + y * y < radius * radius) return circle;
}
}
function dragstarted() {
circles.splice(circles.indexOf(d3.event.subject), 1);
circles.push(d3.event.subject);
d3.event.subject.active = true;
}
function dragged() {
d3.event.subject.x = d3.event.x;
d3.event.subject.y = d3.event.y;
}
function dragended() {
d3.event.subject.active = false;
}
<canvas width="800" height="500"></canvas>
<script src="//d3js.org/d3.v4.min.js"></script>
My ideal solution would be something that allow me to change the color of the overlapping section to another color to represent the intersection between 2 sets.
Thank you in advance
Edit: some updates have been made however Ive only found how to do the coloring for static elements instead of moving
var x1 = 100,
y1 = 100,
x2 = 150,
y2 = 150,
r = 70;
var svg = d3.select('svg')
.append('svg')
.attr('width', 500)
.attr('height', 500);
svg.append('circle')
.attr('cx', x1)
.attr('cy', y1)
.attr('r', r)
.style('fill', 'steelblue')
.style("fill-opacity",0.5)
.style("stroke","black");
svg.append('circle')
.attr('cx', x2)
.attr('cy', y2)
.attr('r', r)
.style('fill', 'orange')
.style("fill-opacity",0.5)
.style("stroke","black");
var interPoints = intersection(x1, y1, r, x2, y2, r);
svg.append("g")
.append("path")
.attr("d", function() {
return "M" + interPoints[0] + "," + interPoints[2] + "A" + r + "," + r +
" 0 0,1 " + interPoints[1] + "," + interPoints[3]+ "A" + r + "," + r +
" 0 0,1 " + interPoints[0] + "," + interPoints[2];
})
.style('fill', 'red')
.style("fill-opacity",0.5)
.style("stroke","black");
function intersection(x0, y0, r0, x1, y1, r1) {
var a, dx, dy, d, h, rx, ry;
var x2, y2;
/* dx and dy are the vertical and horizontal distances between
* the circle centers.
*/
dx = x1 - x0;
dy = y1 - y0;
/* Determine the straight-line distance between the centers. */
d = Math.sqrt((dy * dy) + (dx * dx));
/* Check for solvability. */
if (d > (r0 + r1)) {
/* no solution. circles do not intersect. */
return false;
}
if (d < Math.abs(r0 - r1)) {
/* no solution. one circle is contained in the other */
return false;
}
/* 'point 2' is the point where the line through the circle
* intersection points crosses the line between the circle
* centers.
*/
/* Determine the distance from point 0 to point 2. */
a = ((r0 * r0) - (r1 * r1) + (d * d)) / (2.0 * d);
/* Determine the coordinates of point 2. */
x2 = x0 + (dx * a / d);
y2 = y0 + (dy * a / d);
/* Determine the distance from point 2 to either of the
* intersection points.
*/
h = Math.sqrt((r0 * r0) - (a * a));
/* Now determine the offsets of the intersection points from
* point 2.
*/
rx = -dy * (h / d);
ry = dx * (h / d);
/* Determine the absolute intersection points. */
var xi = x2 + rx;
var xi_prime = x2 - rx;
var yi = y2 + ry;
var yi_prime = y2 - ry;
return [xi, xi_prime, yi, yi_prime];
}
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
<svg width="500" height="500"></svg>
^This works for statics
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
radius = 32;
var circles = d3.range(4).map(function() {
return {
x: Math.round(Math.random() * (width - radius * 2) + radius),
y: Math.round(Math.random() * (height - radius * 2) + radius)
};
});
var color = d3.scaleOrdinal()
.range(d3.schemeCategory20);
svg.selectAll("circle")
.data(circles)
.enter().append("circle")
.style("fill-opacity",0.3)
.style("stroke","black")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 60)
.style("fill", function(d, i) { return color(i); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}
function dragged(d) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("active", false);
}
<svg width="500" height="500"></svg>
<script src="//d3js.org/d3.v4.min.js"></script>
^This is my moving circles that I would like to add said effect on.
Is there any way to combine the two codes to achieve this ?
Thanks again
You can use the intersection function of your static approach (second snippet) inside the dragged function of your dynamic approach (third snippet).
First of all, let's create 2 groups, so the "intersection" path will always be in front of the circles:
var g1 = svg.append("g");
var g2 = svg.append("g");
Now to the important part.
Inside the dragged function, get the position of the other (non-dragged) circle:
var otherCircle = circles.filter(function(e, j) {
return i !== j;
}).datum();
If you have more than two circles you'll have to refactor this, but my demo below has just two circles, so let's move on.
Then, check if they overlap:
Math.hypot(d.x - otherCircle.x, d.y - otherCircle.y) < 2 * radius
If they do, call intersection, and set the path's d attribute:
var interPoints = intersection(d.x, d.y, radius, otherCircle.x, otherCircle.y, radius);
path.attr("d", function() {
return "M" + interPoints[0] + "," + interPoints[2] + "A" + radius + "," + radius +
" 0 0,1 " + interPoints[1] + "," + interPoints[3] + "A" + radius + "," + radius +
" 0 0,1 " + interPoints[0] + "," + interPoints[2];
})
If they don't, erase the path:
path.attr("d", null)
Here is the working demo:
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
radius = 60;
var data = d3.range(2).map(function(d, i) {
return {
x: i ? 200 : 400,
y: 150
};
});
var g1 = svg.append("g");
var g2 = svg.append("g");
var color = d3.scaleOrdinal()
.range(d3.schemeCategory10);
var circles = g1.selectAll("circle")
.data(data)
.enter().append("circle")
.style("fill-opacity", 0.3)
.style("stroke", "black")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", radius)
.style("fill", function(d, i) {
return color(i);
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
var path = g2.append("path")
.style("fill", "white")
.style("stroke", "black")
.attr("d", null);
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}
function dragged(d, i) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
var otherCircle = circles.filter(function(e, j) {
return i !== j;
}).datum();
if (Math.hypot(d.x - otherCircle.x, d.y - otherCircle.y) < 2 * radius) {
var interPoints = intersection(d.x, d.y, radius, otherCircle.x, otherCircle.y, radius);
path.attr("d", function() {
return "M" + interPoints[0] + "," + interPoints[2] + "A" + radius + "," + radius +
" 0 0,1 " + interPoints[1] + "," + interPoints[3] + "A" + radius + "," + radius +
" 0 0,1 " + interPoints[0] + "," + interPoints[2];
})
} else {
path.attr("d", null)
}
}
function dragended(d) {
d3.select(this).classed("active", false);
}
function intersection(x0, y0, r0, x1, y1, r1) {
var a, dx, dy, d, h, rx, ry;
var x2, y2;
/* dx and dy are the vertical and horizontal distances between
* the circle centers.
*/
dx = x1 - x0;
dy = y1 - y0;
/* Determine the straight-line distance between the centers. */
d = Math.sqrt((dy * dy) + (dx * dx));
/* Check for solvability. */
if (d > (r0 + r1)) {
/* no solution. circles do not intersect. */
return false;
}
if (d < Math.abs(r0 - r1)) {
/* no solution. one circle is contained in the other */
return false;
}
/* 'point 2' is the point where the line through the circle
* intersection points crosses the line between the circle
* centers.
*/
/* Determine the distance from point 0 to point 2. */
a = ((r0 * r0) - (r1 * r1) + (d * d)) / (2.0 * d);
/* Determine the coordinates of point 2. */
x2 = x0 + (dx * a / d);
y2 = y0 + (dy * a / d);
/* Determine the distance from point 2 to either of the
* intersection points.
*/
h = Math.sqrt((r0 * r0) - (a * a));
/* Now determine the offsets of the intersection points from
* point 2.
*/
rx = -dy * (h / d);
ry = dx * (h / d);
/* Determine the absolute intersection points. */
var xi = x2 + rx;
var xi_prime = x2 - rx;
var yi = y2 + ry;
var yi_prime = y2 - ry;
return [xi, xi_prime, yi, yi_prime];
}
svg {
background-color: wheat;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="600" height="300"></svg>
SVG mix-blend-mode hover from screen to normal
This isn't exactly what you're looking for since it doesn't let you programmatically control the color of intersecting segments, but CSS mix-blend-mode is a very simple solution I've used with d3. I've tried to accomplish the same thing but ran into performance problems calculating intersections on animating large datasets. Compatibility may be a concern if this needs to work in IE/ Edge, but otherwise most modes are supported in Chrome, Firefox, and Safari (even mobile).
Here's a good guide with examples on d3 as well and a simplified Codepen snippet.
Otherwise it seems like you've already found D3.js - detect intersection area. To get that to work with drag, you'll need to write the calculations to determine which circles are overlapping, then calculate their intersection area.

d3.js node translation does not work when changed the node to image

I want to centralize the node when I clicked it, so I used the following code:
node.on("click", function(d){
var cX = width/2;
var cY = height/2;
var dx = cX - d.x;
var dy = cY - d.y;
link.attr("transform", function(d) { return "translate(" + dx + "," + dy + ")"; });
node.attr("transform", function(d) { return "translate(" + dx + "," + dy + ")"; });
});
It works for the "normal node", but when changed the node to image, the link is moved while the node (image) is still here.
Here is my example: http://talk.huacishu.com/t11.html
I know a little bit about d3js\javascript, anyone can tell me where is wrong?
I finally made it by another way :
Using "var g_container = svg.append(g) "
to append a "g" label(g_container), and move the "g_container" to centralize the clicked node.
here is the final project:
https://myaisv.github.io/curry/renlifang/connan/t1.html

Animating circles with D3.js

So I have been messing around with D3.js for a couple of days now and I have basic circle generation / animation (that act as bubbles), and I was wondering how I could animate the circles on the x axsis, so they wobble back forth as the travel / transition to the top of the page. The current animation can be viewed at chrisrjones.com/bubbles-v1.html
Demo of Solution:
Note the lack of symmetry:
http://jsfiddle.net/blakedietz/R5cRK/1/embedded/result/
Approach:
Determine a mathematical function that would properly model the movement that you want.
In this case we want a sine wave. We can modify aspects of each bubbles characteristics to give a unique movement pattern to each bubble.
Build or find a solution that utilizes the key concepts needed for this problem.
I like to search on bl.ocks.org/mbostock for examples that have the foundational parts of the problem that I'm trying to solve. On the site I found this example:http://bl.ocks.org/mbostock/1371412
Modify the given example to more similarly mirror the specified outcome.
Solution:
Here is a quick demo of a solution. I'll return to this to give you a full walk through. Modifications can be made to make the bubble placement and sizing as well as wiggle random/semi-unique per each bubble.
w = 960,
h = 500;
var svg = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h);
var circle = svg.selectAll("circle")
.data(d3.range(70).map(function(datum,interval) {
return {
x: interval*20,
y: 0,
dx: 5,
dy: -3 * (Math.random()+1),
mu: Math.random()*2
};
}))
.enter().append("svg:circle")
.attr("r", 2.5)
.attr("fill","blue")
.attr("opacity",".5");
var text = svg.append("svg:text")
.attr("x", 20)
.attr("y", 20);
var start = Date.now(),
frames = 0;
d3.timer(function()
{
// Update the FPS meter.
var now = Date.now(), duration = now - start;
text.text(~~(++frames * 1000 / duration));
if (duration >= 1000) frames = 0, start = now;
// Update the circle positions.
circle
.attr("cx", function(d) { d.x += Math.random()*3*Math.sin(Math.random()*3*d.x + Math.random()*10); if (d.x > w) d.x -= w; else if (d.x < 0) d.x += w; return d.x; })
.attr("cy", function(d) { d.y += d.dy ; if (d.y > h) d.y -= h; else if (d.y < 0) d.y += h; return d.y; })
.attr("r",function(d)
{
return (d.y < 100) ? d3.select(this).attr("r") : d.mu*500/d.y;
});
});
You can do that using custom tween function for cx:
var circlesTransition = d3.selectAll("circle")
.transition()
.duration(5000)
.attr("cy", "0")
.attrTween('cx', function (d, i, a) {
return function (t) {
// Add salt, pepper and constants as per your taste
return a + (Math.random() - 0.5) * 10;
};
});

linking nodes of variable radius with arrows

I have some circles/nodes of different radius and I have to connect them with paths having arrow ends.
Here's the code for the marker:
svg.append("svg:defs").selectAll("marker")
.data(["default"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 5)
.attr("refY", -1.5)
.attr("markerWidth", 10)
.attr("markerHeight", 10)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M1,-5L10,0L0,5");
I have stored the radius of circles in an array.
Here's the screen shot:
The arrow is actually "inside" the circles. How do I get the arrows to be at the surface of the circles?
This is an old question, but here is my solution if you want your arrowheads to be at the edge of your nodes instead of on top of or beneath them. My approach was also to draw the path connecting the nodes such that the end points were on the nodes' edges rather than at the nodes' centers. Starting from the Mobile Patent Suits example (http://bl.ocks.org/mbostock/1153292), I replaced the linkArc method with:
function linkArc(d) {
var sourceX = d.source.x;
var sourceY = d.source.y;
var targetX = d.target.x;
var targetY = d.target.y;
var theta = Math.atan((targetX - sourceX) / (targetY - sourceY));
var phi = Math.atan((targetY - sourceY) / (targetX - sourceX));
var sinTheta = d.source.r * Math.sin(theta);
var cosTheta = d.source.r * Math.cos(theta);
var sinPhi = d.target.r * Math.sin(phi);
var cosPhi = d.target.r * Math.cos(phi);
// Set the position of the link's end point at the source node
// such that it is on the edge closest to the target node
if (d.target.y > d.source.y) {
sourceX = sourceX + sinTheta;
sourceY = sourceY + cosTheta;
}
else {
sourceX = sourceX - sinTheta;
sourceY = sourceY - cosTheta;
}
// Set the position of the link's end point at the target node
// such that it is on the edge closest to the source node
if (d.source.x > d.target.x) {
targetX = targetX + cosPhi;
targetY = targetY + sinPhi;
}
else {
targetX = targetX - cosPhi;
targetY = targetY - sinPhi;
}
// Draw an arc between the two calculated points
var dx = targetX - sourceX,
dy = targetY - sourceY,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + sourceX + "," + sourceY + "A" + dr + "," + dr + " 0 0,1 " + targetX + "," + targetY;
}
Note that this code expects an "r," or radius, attribute to be in the node data. To place the points of the arrows at the correct positions, I changed the refX and refY attributes so that the point of the arrow was at the edge of the node:
svg.append("defs").selectAll("marker")
.data(["suit", "licensing", "resolved"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 10)
.attr("refY", 0)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
This is really funny; I just solved this problem yesterday.
What I did is to end the path at the edge of the node, not at the centre.
My case is more complicated because I use Bezier curves, not straight lines, but this might help you:
svg.append("svg:defs").selectAll("marker")
.data(["default"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -3 6 6")
.attr("refX", 5.0)
.attr("refY", 0.0)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-2.0L5,0L0,2.0");
links
.attr("fill", "none")
.attr("d", function(d) {
var tightness = -3.0;
if(d.type == "straight")
tightness = 1000;
// Places the control point for the Bezier on the bisection of the
// segment between the source and target points, at a distance
// equal to half the distance between the points.
var dx = d.target.x - d.source.x;
var dy = d.target.y - d.source.y;
var dr = Math.sqrt(dx * dx + dy * dy);
var qx = d.source.x + dx/2.0 - dy/tightness;
var qy = d.source.y + dy/2.0 + dx/tightness;
// Calculates the segment from the control point Q to the target
// to use it as a direction to wich it will move "node_size" back
// from the end point, to finish the edge aprox at the edge of the
// node. Note there will be an angular error due to the segment not
// having the same direction as the curve at that point.
var dqx = d.target.x - qx;
var dqy = d.target.y - qy;
var qr = Math.sqrt(dqx * dqx + dqy * dqy);
var offset = 1.1 * node_size(d.target);
var tx = d.target.x - dqx/qr* offset;
var ty = d.target.y - dqy/qr* offset;
return "M" + d.source.x + "," + d.source.y + "Q"+ qx + "," + qy
+ " " + tx + "," + ty; // to "node_size" pixels before
//+ " " + d.target.x + "," + d.target.y; // til target
});
By the way; you'll have to do the same for the 'source' arrow head (I only have it at the target)
you may order the svg elements such that the circles will be rendered first, the lines with arrows thereafter (in d3 there is a .ordermethod, see here for details. for the record, the corrsponding part of the raphael api is discussed here).
I search online, none of the answer worked, so I made my own:
Here is the code:
//arrows
svg.append("defs").selectAll("marker")
.data(["suit", "licensing", "resolved"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 9)
.attr("refY", 0)
.attr("markerWidth", 10)
.attr("markerHeight", 10)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5 L10,0 L0, -5")
.style("stroke", "#4679BD")
.style("opacity", "0.6");
//Create all the line svgs but without locations yet
var link = svg.selectAll(".link")
.data(forceData.links)
.enter().append("line")
.attr("class", "link")
.style("marker-end", "url(#suit)");
//Set up the force layout
var force = d3.layout.force()
.nodes(forceData.nodes)
.links(forceData.links)
.charge(-120)
.linkDistance(200)
.size([width, height])
.on("tick", tick)
.start();
function tick(){
link.attr("x1", function (d) { return d.source.x; })
.attr("y1", function (d) { return d.source.y; })
.attr("x2", function (d) {
return calculateX(d.target.x, d.target.y, d.source.x, d.source.y, d.target.radius);
})
.attr("y2", function (d) {
return calculateY(d.target.x, d.target.y, d.source.x, d.source.y, d.target.radius);
});
d3.selectAll("circle")
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; });
d3.select("#forcelayoutGraph").selectAll("text")
.attr("x", function (d) { return d.x; })
.attr("y", function (d) { return d.y; });
}
function calculateX(tx, ty, sx, sy, radius){
if(tx == sx) return tx; //if the target x == source x, no need to change the target x.
var xLength = Math.abs(tx - sx); //calculate the difference of x
var yLength = Math.abs(ty - sy); //calculate the difference of y
//calculate the ratio using the trigonometric function
var ratio = radius / Math.sqrt(xLength * xLength + yLength * yLength);
if(tx > sx) return tx - xLength * ratio; //if target x > source x return target x - radius
if(tx < sx) return tx + xLength * ratio; //if target x < source x return target x + radius
}
function calculateY(tx, ty, sx, sy, radius){
if(ty == sy) return ty; //if the target y == source y, no need to change the target y.
var xLength = Math.abs(tx - sx); //calculate the difference of x
var yLength = Math.abs(ty - sy); //calculate the difference of y
//calculate the ratio using the trigonometric function
var ratio = radius / Math.sqrt(xLength * xLength + yLength * yLength);
if(ty > sy) return ty - yLength * ratio; //if target y > source y return target x - radius
if(ty < sy) return ty + yLength * ratio; //if target y > source y return target x - radius
}

Categories

Resources