D3.js v4 Force layout and fixed node anomaly - d3.js

I have a D3.js V4 Force layout, comprising two nodes linked by a single edge. One node is fixed near top left, the other free to move.
When the layout is run, the non-fixed node starts in the middle of the layout, and moves away from the fixed node, as though repelled. It ends up in the opposite corner from the fixed node.
I would expect the free node to end up between the centre of the layout (where gravity pulls it) and the fixed node (pulled towards it by the link force). What am I missing, please?
var width = 240,
height = 150;
var nodes = [
{ // Larger node, fixed
fx: 20, fy: 20, r: 10
},
{ // Small node, free
r: 5
}];
var links = [{ // Link the two nodes
source: 0, target: 1
}
];
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height);
// Create simulation with layout-centring and link forces
var force = d3.forceSimulation()
.force("centre", d3.forceCenter(width / 2, height / 2))
.force("link", d3.forceLink())
.nodes(nodes);
force.force("link").links(links);
// Draw stuff
var link = svg.selectAll('.link')
.data(links)
.enter().append('line')
.attr('class', 'link');
var node = svg.selectAll('.node')
.data(nodes)
.enter().append('circle')
.attr('class', 'node')
force.on('tick', function() {
node.attr('r', function(d) {
return d.r;
})
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
});
link.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;
});
});
<script src="https://d3js.org/d3.v4.min.js"></script>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<style>
.node {
fill: #f00;
stroke: #fff;
stroke-width: 2px;
}
.link {
stroke: #777;
stroke-width: 2px;
}
</style>
</head>
<body>
</body>
</html>

The Centering force will try to have the center of mass of all nodes in the coordinate given. As you have 2 nodes and 1 is fixed, the other will be symmetric to the fixed node.
d3 Centering force documentation
Centering
The centering force translates nodes uniformly so that the mean
position of all nodes (the center of mass if all nodes have equal
weight) is at the given position ⟨x,y⟩.

Related

How to draw a line dynamically between two circles

I have drawn two circles with the below code, i have to draw a line between these two circles but the tricky point when i start to draw a line from the first circle there should be availability of second circle then only it should draw otherwise it shouldn't draw line and vice versa. if i click outside of circle then also it shouldn't draw line
in my below code or fiddle check it i can a draw a line my condition is not working
var line;
var svg = d3.select("body").append("svg")
.attr("width", 600)
.attr("height", 400).on("mousedown", mousedown).on("mouseup", mouseup);
function mousedown() {
var m = d3.mouse(this);
line = vis.append("line")
.attr("x1", m[0])
.attr("y1", m[1])
.attr("x2", m[0])
.attr("y2", m[1]);
svg.on("mousemove", mousemove);
}
function mousemove() {
var m = d3.mouse(this);
line.attr("x2", m[0])
.attr("y2", m[1]);
}
function mouseup() {
svg.on("mousemove", null);
}
var inputs = [
{ "x" : 200, "y" : 150, r : 50},
{ "x" : 300, "y" : 250, r : 50},
]
svg.selectAll("circle").data(inputs).enter().append("circle")
.attr("r", function(d, i){ return d.r })
.attr("cx", function(d, i){ return d.x })
.attr("cy", function(d, i){ return d.y })
.attr("stroke", "red")
.attr("fill", "white")
Here is my fiddle : https://jsfiddle.net/34j6pkn9/1/
maybe this will help you but its far from a good solution, but it work
Note :
circle drawing its bit ridiculus for me, it draw rect, imagine you
draw rect first then you draw a circle inside of it that its why, it
have bug on each angle of it that look like circle but difinetly its
a reactangle,
i think it can be solve by some calculation,but sorry i didnt know any of
that
var line;
var mx =0
var my =0
var inputs = [
{ "x" : 200, "y" : 150, r : 50},
{ "x" : 300, "y" : 250, r : 50},
]
var vis = d3.select("body").append("svg")
.attr("width", 600)
.attr("height", 400)
.on("mouseup", mouseup);
function mousedown() {
var m = d3.mouse(this);
line = vis.append("line")
.attr("x1", m[0])
.attr("y1", m[1])
.attr("x2", m[0])
.attr("y2", m[1]);
mx = m[0]
my = m[1]
vis.on("mousemove", mousemove);
}
function mousemove() {
var m = d3.mouse(this);
line.attr("x2", m[0])
.attr("y2", m[1]);
}
function mouseup() {
var m = d3.mouse(this);
//console.log(mx-m[0],my-m[1])
inputs.forEach(function(d,i){
if(m[0]<(d.x+d.r)&& m[0]>(d.x-d.r)&&m[1] <(d.y+d.r)&& m[1]>(d.y-d.r)){
if(mx<(d.x+d.r)&& mx>(d.x-d.r)&&my <(d.y+d.r)&& my>(d.y-d.r)){
}else{
vis.on("mousemove", null);
}
}
})
}
vis.selectAll("circle").data(inputs).enter().append("circle")
.attr("r", function(d, i){ return d.r })
.attr("cx", function(d, i){ return d.x })
.attr("cy", function(d, i){ return d.y })
.attr("stroke", "red")
.attr("fill", "white")
.on("mousedown", mousedown).on("mouseup", mouseup);
svg {
border: 1px solid red;
}
line {
stroke: steelblue;
stroke-width: 2px;
stroke-linecap: round;
}
<html>
<head>
<meta charset="utf-8">
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.3/d3.min.js"></script>
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head><body>
</body>
There will be a line after every circle (except the first one). Each line will connect the centers of every two consecutive circles.
You've the first circle which you are appending in your svg now, but how will you find the previous circle? You can, simply, find previousElementSibling of the first circle. But, there is catch here, notice there is line after every circle. So, be sure to check the name of the previousElementSibling if it's "line", find previousElementSibling once more.
// At this point, you've just appended the circle in
// 'svg' based on where the mouse is etc.
// Now, find the previous circle.
const parentNode = document.querySelector("svg");
// 'thisCircle' is the one you're appending in svg now.
const thisCircle = parentNode.lastElementChild;
// Previous sibling of this circle.
const previousSibling = thisCircle.previousElementSibling;
// If this is the first circle drawn, then do nothing- return
if (!previousSibling) {
return;
}
// Check if previous sibling is indeed 'circle' and not 'line'
const nodeNameOfPreviousSiblig = previousSibling.nodeName;
let previousCircle = previousSibling;
// If the previous node is 'line' go one more step upwards- it must be a circle. Since pattern => 1 circle, 1 line.
if (nodeNameOfPreviousSiblig == 'line') {
previousCircle = previousSibling.previousElementSibling;
}
// center of the previous circle.
const xCoordOPrevCircleCenter = previousCircle.getAttribute("cx");
const yCoordOPrevCircleCenter = previousCircle.getAttribute("cy");
Now, finally, append the line connecting these two consecutive circles centers:
svg.append('line')
.attr('x1', xCoordOThisCircleCenter)
.attr('y1', yCoordOThisCircleCenter)
.attr('x2', xCoordOPrevCircleCenter)
.attr('y2', yCoordOPrevCircleCenter)

Using d3.behavior.drag, How to keep a drag image visible outside of the svg element

Using d3.behavior.drag(), is there a way to enable a dragged image to be visible outside of its parent svg element borders.
In my app, I have a top layout based on an HTML grid (using flexBox) and several D3.js graphs located in each grid cell. Each graph is built with an SVG element and its childrens.
I need a drag and drop feature to enable copy/move of elements between these graphs. As now, the feature is working except that the drag image disappears when I cross the border of the source graph.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
float: left;
border-bottom: solid 1px #ccc;
border-right: solid 1px #ccc;
margin-right: -1px;
margin-bottom: -1px;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var width = 240,
height = 125,
radius = 20;
var overSVG;
var drag = d3.behavior.drag()
.origin(function (d) {
return d;
})
.on("drag", dragmove)
.on("dragend", dragend);
var svg = d3.select("body").append("div").selectAll("svg")
.data(d3.range(2).map(function (v, i) {
return {
svgElement: i,
x : width / 2,
y : height / 2
};
}))
.enter().append("svg")
.attr("width", width)
.attr("height", height)
.attr("id", function (d, i) {
return "svg_" + i
})
.on("mouseover", over)
.on("mouseout", out);
svg.append("circle")
.attr("r", radius)
.attr("cx", function (d) {
return d.x;
})
.attr("cy", function (d) {
return d.y;
})
.attr("svgElement", function (d, i) {
return i;
})
.call(drag);
function over(d, i) {
overSVG = d;
var selectedNodeId = "#svg_" + i;
d3.select(selectedNodeId)
.attr("fill", 'red');
}
function out(d, i) {
overSVG = "";
var selectedNodeId = "#svg_" + i;
d3.select(selectedNodeId)
.attr("fill", 'blue');
}
function dragmove(d) {
d3.select(this)
.attr("cx", d.x = d3.event.x)
.attr("cy", d.y = d3.event.y);
console.log("drag move", this, d3.event.x, ' ', d3.event.y);
}
function dragend(d) {
console.log("==>drag Ended :");
console.log(" dragged circle", this);
console.log(" from svg ", d);
console.log(" to", overSVG);
}
</script>
By default the svg elements have an overflow attribute set to hidden.
You can try setting the overflow attribute to visible
svg {
overflow: visible;
}
Again, without seeing a working example, it's hard to tell if this will work for you, but maybe it can help.

d3 force layout nodes are pinning on dblick but appearance does not modify

Using and playing around with the Les Miserables Json data, I coded from modifications in d3. I was able to code everything except highlighting/marking a node if it is in the "fixed" state (vs the force graph layout). When you double click a node, it will freeze, a drag and move will pin it somewhere while all the other nodes (by default) in the force layout. When you double click the node again, the node is unpinned, and floating in the force layout as usual. The second thing I wanted to do is that if the node is in a “pinned” state, the node should be a different color, or highlighted somehow. I tried a few ways within the method chaining, checking with conditional statements for the node state to modify the node features, but they are not working. I also separated out the dblclick handler as an outside function (which is my current version.
I put my code here of the original attempt within the method chains:
http://pastebin.com/SqrqgVET
I also tried another way, using a variable “pinned” to determine the state of the node, within the double-click, to modify the nodes border, and change the state accordingly but that is not changing the node’s appearance either. There is a console.log() statement, and the function is going into the if else portions accordingly.
http://pastebin.com/dzEw42mQ
Below is the current version.
Any feedback would be great. Thanks!
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
cursor: move;
stroke: #fff;
stroke-width: 1.5px;
}
.node.fixed {
fill: #f00;
}
.link {
stroke: #999;
stroke-opacity: .6;
}
</style>
<head>
<title>Victor Hugo Had No Internet</title>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="http://d3js.org/colorbrewer.v1.min.js"></script>
</head>
<body>
<script type="text/javascript">
//Size of region to render on
var width = 960,
height = 500;
var color = d3.scale.ordinal()
.domain([1, 10])
.range(colorbrewer.BrBG[9]);
//D3 force directed layout
//Try playing with the charge and link distance
var force = d3.layout.force()
.charge(-100)
.linkDistance(40)
//.on("tick", tick) //event ADDED
.size([width, height]);
//Add our canvas
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
//Select the miserables data ;)
d3.json("miserables.json", function(error, graph) {
force
.nodes(graph.nodes)
.links(graph.links)
.start();
//Add the links
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
//Add the nodes
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", function(d){ return Math.sqrt(d.coolness);})
.attr("stroke", "#ffffff")
.on("dblclick", dblclick)
.call(force.drag)
.style("fill", function(d) {return color(d.group);}); //});
//.style("border", 5);//function(d) {
//if (d.fixed==false) {return 4}; });
// node.append("text") //label
// .attr("dx", 6)
// .attr("dy", ".10em")
// .text(function(d) { return d.name; });
//add labels
var labels = svg.selectAll("text")
.data(graph.nodes)
.enter()
.append("text")
.attr({"x":function(d){return d.x;},
"y":function(d){return d.y;}})
.text(function(d) {
if (d.coolness>25 && d.name.length > 6) {return d.name.substring(0,6)+'...'}
if (d.coolness>25 && d.name.length < 6) {return d.name}
else { return null } ;})
.call(force.drag);
//Update stuff for animation:
// This takes the physics simulation for the force directed graph and
// sets the location of the nodes and edges to the new positions
force.on("tick", function() {
link.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; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
labels.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
});
// action to take on mouse double click
function dblclick(d) { //color, stroke not working.
//var pinned = d3.select(this).attr("stroke");
console.log("dblclick")
if (d.fixed == true) { //pinned state
console.log("pinned")
d3.select(this)
.attr("stroke", "#ffffff")
.attr("stroke-width", 1.5)
.classed("fixed", d.fixed = false);//now unpin
} else { //else not pinned state
console.log("not pinned")
d3.select(this)
.attr("stroke", "#000000")
.attr("stroke-width", 4)
.classed("fixed", d.fixed = true);
}
}//end dbl click
});
</script>`
Here is the output of the console log (where I added console.log(this) to check the right element is being passed to the dblclick function and if parameters are being set accordingly:
[Log] dblclick (miserables_graph.html, line 131)
[Log] <circle class=​"node fixed" r=​"9.1104335791443" stroke=​"#000000" style=​"fill:​ #f6e8c3;​" cx=​"630.38114584665" cy=​"98.39845698676822" stroke-width=​"4">​</circle>​ (miserables_graph.html, line 132)
[Log] not pinned (miserables_graph.html, line 140)
[Log] dblclick (miserables_graph.html, line 131)
[Log] <circle class=​"node" r=​"9.1104335791443" stroke=​"#ffffff" style=​"fill:​ #f6e8c3;​" cx=​"630.38114584665" cy=​"98.39845698676822" stroke-width=​"1.5">​</circle>​ (miserables_graph.html, line 132)
[Log] pinned (miserables_graph.html, line 134)

How to draw a line / link between two points on a D3 map based on latitude / longitude?

I am attempting to create a map of the 10 major NASA facilities in D3. I have successfully generated the base United States map and appended NASA logos at each one of the center locations based on a .csv with latitude and longitude. However, I cannot figure out any elegant way to draw lines / links / arcs / connections between the points on the map.
In the code below, I have drawn a line between GSFC and KSC (using the 'var = places', 'var = route', and 'svg.append("path")') but it is on an SVG layer, so it is on top of the logos (which looks awful) and does not scale (or go away would be fine, too) when clicking to zoom in on a state. I would like to be able to draw links between the centers based on the latitude and longitude data from the .csv.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.background {
fill: none;
pointer-events: all;
}
#states {
fill: #aaaaaa;
}
#states .active {
fill: #ff0000;
fill-opacity: .5;
}
#state-borders {
fill: none;
stroke: #ffffff;
stroke-width: 1.5px;
stroke-linejoin: round;
stroke-linecap: round;
pointer-events: none;
}
path.link {
fill: none;
stroke: #666666;
stroke-width: 1.5px;
}
.stroke {
fill: none;
stroke: #000;
stroke-width: 3px;
}
.fill {
fill: #fff;
}
.graticule {
fill: none;
stroke: #777;
stroke-width: .5px;
stroke-opacity: .5;
}
.route {
fill: none;
stroke: blue;
stroke-width: 3px;
}
</style>
<body>
<h2>
<span>NASA Centers</span>
</h2>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/d3.geo.projection.v0.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script>
var width = 1000,
height = 600,
centered;
var projection = d3.geo.albersUsa()
.scale(1070)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var graticule = d3.geo.graticule();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var g = svg.append("g");
var places = {
GSFC: [-76.852587, 38.991621],
KSC: [-80.650813, 28.524963]
};
var route = {
type: "LineString",
coordinates: [
places.GSFC,
places.KSC
]
};
var point = svg.append("g")
.attr("class", "points")
.selectAll("g")
.data(d3.entries(places))
.enter().append("g")
.attr("transform", function(d) { return "translate(" + projection(d.value) + ")"; });
point.append("text")
.attr("y", 5)
.attr("dx", "1em")
.text(function(d) { return d.key; });
d3.json("us.json", function(error, us) {
g.append("g")
.attr("id", "states")
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path)
.on("click", clicked);
g.append("path")
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
.attr("id", "state-borders")
.attr("d", path);
d3.csv("nasacenters.csv", function(error, data) {
g.selectAll("image").data([0])
.data(data)
.enter()
.append("image")
.attr("xlink:href", "nasalogo.png")
.attr("width", "30")
.attr("height", "30")
.attr("x", function(d) {
return projection([d.lon, d.lat])[0]-15;
})
.attr("y", function(d) {
return projection([d.lon, d.lat])[1]-15;
})
svg.append("path")
.datum(route)
.attr("class", "route")
.attr("d", path)
.style("opacity", 0.5);
});
});
function clicked(d) {
var x, y, k;
if (d && centered !== d) {
var centroid = path.centroid(d);
x = centroid[0];
y = centroid[1];
k = 4;
centered = d;
} else {
x = width / 2;
y = height / 2;
k = 1;
centered = null;
}
g.selectAll("path")
.classed("active", centered && function(d) { return d === centered; });
g.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
.style("stroke-width", 1.5 / k + "px");
}
</script>
</body>
</html>
The .csv file is in the following format:
code,center,lat,lon
GSFC,Goddard Space Flight Center,38.991621,-76.852587
KSC,Kennedy Space Center,28.524963,-80.650813
JPL,Jet Propulsion Laboratory,34.200463,-118.176008
DFRC,Dryden Flight Research Center,34.613714,-118.076790
GRC,Glenn Research Center,41.415891,-81.861774
MSFC,Marshall Space Flight Center,34.646554,-86.674368
ARC,Ames Research Center,37.409574,-122.064292
LaRC,Langley Research Center,37.092123,-76.376230
JSC,Johnson Space Center,29.551508,-95.092256
SSC,Stennis Space Center,30.363692,-89.600036
I modified your example slightly based on the problems you described: http://bl.ocks.org/erikhazzard/6201948
It looks like there are three issues:
Paths draw on top of icon. To fix this, you can change the order of when you add items to the group, or add sub groups to your main g group, ensuring the order that you add the groups matches the order you want things to appear.
The paths between points doesn't zoom when you zoom the map. To fix this, make sure to add everything to the group that you're modifying the clicked() function. In this case, your g group is being zoomed on, so if you add the paths to the g group instead of the svg directly the paths will zoom as well. In the example provided, text does also not zoom in - that's because it's added directly to the SVG and not the g group that is being transformed.
Paths aren't created automatically from the data. To fix this, you can generate an array containing LineString objects from the data. For example,
for(var i=0, len=data.length-1; i<len; i++){
// (note: loop until length - 1 since we're getting the next
// item with i+1)
links.push({
type: "LineString",
coordinates: [
[ data[i].lon, data[i].lat ],
[ data[i+1].lon, data[i+1].lat ]
]
});
}
Then, do the standard data join pattern and pass in the links list to the data. When you pass in path as the d attribute, it will generate a great arc based on the coordinates for each item:
// Standard enter / update
var pathArcs = arcGroup.selectAll(".arc")
.data(links);
//enter
pathArcs.enter()
.append("path").attr({
'class': 'arc'
}).style({
fill: 'none',
});
//update
pathArcs.attr({
//d is the points attribute for this path, we'll draw
// an arc between the points using the arc function
d: path
})
.style({
stroke: '#0000ff',
'stroke-width': '2px'
})
In my example ( http://bl.ocks.org/enoex/6201948 ) I added a transition on the great arc paths to illustrate how the path is drawn based on the order of coordinate pairs passed into the links object.
Hope that helps!

d3.js force layout doesn't run

I'm just starting with d3.js and can't get a simple demo to work correctly.
It's tough to debug: If there’s a bug, it often crashes in d3′s minified code with no stack trace. In this case, it doesn’t print any errors to the error console at all.
When I run this code, all my nodes are stuck in position (0,0) instead of being laid out by force(). What's wrong?
<html>
<meta charset="UTF-8">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
$(function() {
var svg = d3.select('#graph').append('svg').attr('width', 900).attr('height', 900)
var myNodes = [{name:'a'}, {name:'b'}, {name:'c'}]
var myLinks = [{source:myNodes[0], target:myNodes[1]}, {source:myNodes[1], target:myNodes[2]}, {source:myNodes[2], target:myNodes[0]}]
svg.append("text").text("myNodes[0].name=" + myNodes[0].name).attr('y', 50)
var force = d3.layout.force().charge(-120).linkDistance(30).size([900, 900])
var link = svg.selectAll("line")
.data(myLinks)
.enter().append("svg:line");
var node = svg.selectAll("circle")
.data(myNodes)
.enter().append("svg:circle")
.attr("r", 6)
.call(force.drag)
force.nodes(myNodes).links(myLinks).start()
});
</script>
</head>
<body>
<div id="graph"></div>
</body>
</html>
Force just handles setting x/y attributes on the nodes and links:
... The initial x and y coordinates, if not already set externally to a valid number, are computed by examining neighboring node..
It does not actually handle updating of the corresponding svg shapes. This you would typically do in the tick event:
Listen to tick events to update the displayed positions of nodes
force.on("tick", function() {
link.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; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
The only other piece missing from your example is setting the style of the lines. By default they have no stroke color so appear invisible. You can either set the color directly on the line, like this:
var link = svg.selectAll("line")
.data(myLinks)
.enter().append("svg:line")
.attr('stroke', 'red')
.attr('stroke-width', 2)
Or make a CSS class for them:
<style>
.link {
stroke: blue;
stroke-width: 1.5px;
}
</style>
And tag them with it when you create them:
var link = svg.selectAll("line")
.data(myLinks)
.enter().append("svg:line")
.attr('class', 'link')

Resources