JS / d3.js: Steps to Highlighting of adjacent links - javascript

Good day,
My earlier question for this project would be:
D3.js: Dynamically generate source and target based on identical json values
I am new to d3.js, especially in the data manipulation and node graphs area.. I would like to ask several questions regarding data manipulation with regards to creating node graphs. While carrying out my project, here are several problems I have encountered, leading to several questions:
1) Must the source and target values be unique in nature?
Will the linking work if the source/target values are non-unique?
2) A way to highlight/change attributes of links connected to the current selected node
So far, I am only able to change the current node's properties using:
var simulation = d3.forceSimulation(graphData)
.force("charge", d3.forceManyBody().strength(-300))
.force("link", d3.forceLink().id(function(d) { return d[idSel]; }).distance(70))
.force("x", d3.forceX(width/2))
.force("y", d3.forceY(height/2))
.on("tick", ticked);
var g = svg.append("g"),
link = g.append("g").attr("stroke-width", 1.5).selectAll(".link"),
node = g.append("g").attr("stroke-width", 1.5).selectAll(".node");
simulation.nodes(graphData);
simulation.force("link").links(links);
link = link
.data(links)
.enter().append("line")
.attr("class", "link");
node = node
.data(graphData)
.enter().append("circle")
.attr("class", "node")
.attr("r", 6)
.style("fill", function(d, i) { return colors(d[idSel]); })
.on("click", function (d, i, l) {
//Node Effect - Change only selected node's size
d3.selectAll(".node").attr("r", 6);
d3.select(this).attr("r", 12);
//Link Effect - Highlight adjacent Links
... Need help here ...
});
function ticked()
{
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; });
}
I have gotten the simulation example from: http://bl.ocks.org/mbostock/1095795
However, I do understand this is inefficient as well, but I have no other way to do it given my limited knowledge on d3..
Is there a good example around for me to learn how to highlight the links as well?
Also, I see that I have to use:
function restart()
{
node.exit().remove();
link.exit().remove();
simulation.nodes(nodes);
simulation.force("link").links(links);
simulation.alpha(1).restart();
}
To restart the simulation, else any errors will result in the program unable to calculate the x/y values. However, when I implement this code as a restart function, the newly created nodes do not have x/y values anymore.. Am i doing something wrong?
So sorry for the vague question.. any guidance is very much appreciated. Thank you SO Community! :)

Answering only the question about how to highlight the links (since you didn't provide the links array, here is an answer based on your previous code):
node.on("click", function(d) {
var thisNode = d.id
d3.selectAll(".circleNode").attr("r", 6);
d3.select(this).attr("r", 12);
link.attr("opacity", function(d) {
return (d.source.id == thisNode || d.target.id == thisNode) ? 1 : 0.1
});
});
What does this code do?
First, we get the id of the clicked node:
var thisNode = d.id
Then, we scan the links to see if the source or the target has the same id:
(d.source.id == thisNode || d.target.id == thisNode)
If that is true, we set the opacity using a ternary operator:
(condition) ? 1 : 0.1
Here is the demo, click on the nodes:
var nodes = [{
"id": "red",
"value": "1"
}, {
"id": "orange",
"value": "2"
}, {
"id": "yellow",
"value": "3"
}, {
"id": "green",
"value": "1"
}, {
"id": "blue",
"value": "1"
}, {
"id": "violet",
"value": "3"
},{
"id": "white",
"value": "1"
},{
"id": "gray",
"value": "1"
},{
"id": "teal",
"value": "3"
}
];
var links = [];
for (var i = 0; i < nodes.length; i++) {
for (var j = i + 1; j < nodes.length; j++) {
if (nodes[i].value === nodes[j].value) {
links.push({
source: nodes[i].id,
target: nodes[j].id
});
}
}
};
var width = 300,
height = 300;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.id;
}).distance(50))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(links)
.enter().append("line")
.attr("stroke-width", 1)
.attr("stroke", "gray")
.attr("fill", "none");
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", 6)
.attr("class", "circleNode")
.attr("stroke", "gray")
.attr("fill", function(d) {
return d.id;
});
node.on("click", function(d) {
var thisNode = d.id
d3.selectAll(".circleNode").attr("r", 6);
d3.select(this).attr("r", 12);
link.attr("opacity", function(d) {
return (d.source.id == thisNode || d.target.id == thisNode) ? 1 : 0.1
});
});
simulation
.nodes(nodes)
.on("tick", ticked);
simulation.force("link")
.links(links);
function ticked() {
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;
});
}
<script src="https://d3js.org/d3.v4.min.js"></script>

Related

How do I have a specific d3 node be an image in a force directed graph?

I have made a force directed graph which looks similar to this.
I would like the Nine Inch Nails node, in the centre, to be an image but the rest of the nodes to just be circles. Following this answer it seemed not too difficult but I just can't get my head around the combonation of svg, defs, patterns and d3.
My code is:
var simulation =
d3.forceSimulation()
.force("charge", d3.forceManyBody().strength(-50))
.force("collide", d3.forceCollide().radius(function (d) { return 15 - d.group}).strength(2).iterations(2))
.force("link", d3.forceLink().id(function(d, i) { return i;}).distance(20).strength(0.9))
.force("center", d3.forceCenter(width/2, height/2))
.force('X', d3.forceX(width/2).strength(0.15))
.force('Y', d3.forceY(height/2).strength(0.15));
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
var defs = svg.append('svg:defs');
defs.append("svg:pattern")
.attr("id", "vit-icon")
.attr("width", 48)
.attr("height", 48)
.attr("patternUnits", "userSpaceOnUse")
.append("svg:image")
.attr("xlink:href", "http://placekitten.com/g/48/48")
.attr("width", 48)
.attr("height", 48)
.attr("x", width/2)
.attr("y", height/2)
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("id", function(d, i) { return 'c'+i})
.attr("r", radius)
.attr("fill", function(d) {
if(d.group==0) {return "url(#vit-icon)";}
else {return color(d.group); }
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
As I say, it seems straight forward in my mind. Basically what I think I'm trying to do is have the image in the svg pattern, then use an if statement to tell my root node to use the image url rather than fill with colour.
In dev tools I can see inspect the image and it shows the blue area it would take up and the node I want it to be associatated to has the 'url(#vit-icon)' as it's fill attribute. But it is not showing the image or any fill for that node.
What am I doing wrong? Or is this the complete wrong approach?
Thanks.
In your defs, just change:
.attr("x", width/2)
.attr("y", height/2)
To:
.attr("x", 0)
.attr("y", 0);
Here is a demo:
var nodes = [{
"id": 1,
}, {
"id": 2,
}, {
"id": 3,
}, {
"id": 4,
}, {
"id": 5,
}, {
"id": 6,
}, {
"id": 7,
}, {
"id": 8,
}];
var links = [{
source: 1,
target: 2
}, {
source: 1,
target: 3
}, {
source: 1,
target: 4
}, {
source: 2,
target: 5
}, {
source: 2,
target: 6
}, {
source: 1,
target: 7
}, {
source: 7,
target: 8
}];
var index = 10;
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
node,
link;
var defs = svg.append('svg:defs');
defs.append("svg:pattern")
.attr("id", "vit-icon")
.attr("width", 1)
.attr("height", 1)
.append("svg:image")
.attr("xlink:href", "http://66.media.tumblr.com/avatar_1c725152c551_128.png")
.attr("width", 48)
.attr("height", 48)
.attr("x", 0)
.attr("y", 0);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.id;
}).distance(100))
.force("collide", d3.forceCollide(50))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
link = svg.selectAll(".link")
.data(links, function(d) {
return d.target.id;
})
link = link.enter()
.append("line")
.attr("class", "link");
node = svg.selectAll(".node")
.data(nodes, function(d) {
return d.id;
})
node = node.enter()
.append("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append("circle")
.attr("r", d=> d.id === 1 ? 24 : 14)
.style("fill", function(d) {
if (d.id === 1) {
return "url(#vit-icon)";
} else {
return "teal"
}
})
simulation
.nodes(nodes)
.on("tick", ticked);
simulation.force("link")
.links(links);
function ticked() {
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("transform", function(d) {
return "translate(" + d.x + ", " + d.y + ")";
});
}
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart()
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = undefined;
d.fy = undefined;
}
.link {
stroke: #aaa;
}
.node {
pointer-events: all;
stroke: none;
stroke-width: 40px;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="500" height="300"></svg>

D3.js - Highlight chart elements when interacting with the legend & vice versa

I currently have a hover effect on each node, as well as a hover effect on each element of the legend.
I want to execute the effect when the user interacts with the legend so the relevant node on the chart is highlighted and visa versa. I also want to toggle the effect on and off.
So when the user clicks/hovers over a node the relevant legend item is highlighted in grey.
And when the user clicks/hovers over a legend item the relevant node is highlighted with a black stroke.
I have had a look at the following (Selectable Elements) but can't work out how to apply this to my code.
My understanding so far is that I need to apply a class (something like .selected) to the related element on mouseover & when toggled.
JSFiddle
//Nodes V2
var width = 300,
height = 300,
colors = d3.scale.category20b();
var force = d3.layout.force()
.gravity(.2)
.charge(-3000)
.size([width, height]);
//Svg Chart SVG Settings
var svg = d3.select("#chart").append("svg:svg")
.attr("width", width)
.attr("height", height);
var root = getData();
var nodes = flatten(root),
links = d3.layout.tree().links(nodes);
nodes.forEach(function(d, i) {
d.x = width/2 + i;
d.y = height/2 + 100 * d.depth;
});
root.fixed = true;
root.x = width / 2;
root.y = height / 2;
force.nodes(nodes)
.links(links)
.start();
var link = svg.selectAll("line")
.data(links)
.enter()
.insert("svg:line")
.attr("class", "link");
var node = svg.selectAll("circle.node")
.data(nodes)
.enter()
.append("svg:circle")
.attr("r", function(d) { return d.size/200; })
//.attr('fill', function(d) { return d.color; }) // Use Data colors
.attr('fill', function(d, i) { return colors(i); }) // Use D3 colors
.attr("class", "node")
.call(force.drag)
.on('click', function(){
d3.select( function (d){
return i.li;
})
.style('background', '#000')
})
//Adding an event - mouseover/mouseout
.on('mouseover', function(d) {
d3.select(this)
.transition()//Set transition
.style('stroke', '#222222')
.attr("r", function(d) { return (d.size/200) + 2; })
})
.on('mouseout', function(d) {
d3.select(this)
.transition()
.style('stroke', '#bfbfbf')
.attr("r", function(d) { return (d.size/200) - 2; })
d3.select('ul')
});
//Add a legend
var legend = d3.select('#key').append('div')
.append('ul')
.attr('class', 'legend')
.selectAll('ul')
.data(nodes)
.enter().append('li')
.style('background', '#ffffff')
.text(function(d) { return d.name; })
.on('mouseover', function(d) {
d3.select(this)
.transition().duration(200)//Set transition
.style('background', '#ededed')
})
.on('mouseout', function(d) {
d3.select(this)
.transition().duration(500)//Set transition
.style('background', '#ffffff')
})
.append('svg')
.attr('width', 10)
.attr('height', 10)
.style('float', 'right')
.style('margin-top', 4)
.append('circle')
.attr("r", 5)
.attr('cx', 5)
.attr('cy', 5)
.style('fill', function(d, i) { return colors(i); });
//Add Ticks
force.on("tick", function(e) {
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; });
});
//Center Node
var userCenter = d3.select("svg").append("svg:circle")
.attr('class', 'user')
.attr('r', 30)
.attr('cx', width/2)
.attr('cy', height/2)
.style('fill', '#bfbfbf')
var label = d3.select('svg').append("text")
.text('USER')
.attr('x', width/2)
.attr('y', height/2)
.attr('text-anchor','middle')
.attr('transform', 'translate(0, 5)')
.style('font-size','12px')
.attr('fill','#666666')
//Fix Root
function flatten(root) {
var nodes = [];
function recurse(node, depth) {
if (node.children) {
node.children.forEach(function(child) {
recurse(child, depth + 1);
});
}
node.depth = depth;
nodes.push(node);
}
recurse(root, 1);
return nodes;
}
//Data
function getData() {
return {
"name": "flare",
"size": 0,
"children": [
{ "name": "Jobs", "size": 3743 },
{ "name": "Contact", "size": 3302 },
{ "name": "Dietary", "size": 2903 },
{ "name": "Bookings", "size": 4823 },
{ "name": "Menu", "size": 3002 },
{ "name": "Cards", "size": 3120 },
{ "name": "Newsletter", "size": 3302 }
]
};
}
You should use the same event(mouseover, mouseout) functions for the nodes and legend items.
//Adding an event - mouseover/mouseout
.on('mouseover', onMouseover)
.on('mouseout', onMouseout);
...
//Legend
.on('mouseover', onMouseover)
.on('mouseout', onMouseout)
Then, you use the data passed to the functions to select the correct elements to change the style.
function onMouseover(elemData) {
d3.select("svg").selectAll("circle")
.select( function(d) { return d===elemData?this:null;})
.transition()//Set transition
.style('stroke', '#222222')
.attr("r", function(d) { return (d.size/200) + 2; })
d3.select('#key').selectAll('li')
.select( function(d) { return d===elemData?this:null;})
.transition().duration(200)//Set transition
.style('background', '#ededed')
}
function onMouseout(elemData) {
d3.select("svg").selectAll("circle")
.select( function(d) { return d===elemData?this:null;})
.transition()
.style('stroke', '#bfbfbf')
.attr("r", function(d) { return (d.size/200) - 2; })
d3.select('#key').selectAll('li')
.select( function(d) { return d===elemData?this:null;})
.transition().duration(500)//Set transition
.style('background', '#ffffff')
}
Here is the updated JSFiddle

d3 v4 update/merge grouped data

I am trying to update data for a force simulation in D3 v4, similar to this unsuccessful attempt and semi-similar to this successful one. CodePen here
Instead of joining the new nodes it appears to be doubling all the nodes (see pictures). It does not seem to be refreshing the graph correctly either.
(initial graph)
(after adding data)
HTML:
<button onclick="addData()">Add Data</button>
<svg width="960" height="600"></svg>
JavaScript:
function addData() {
graph.nodes.push({"id": "Extra1", "group": 11},{"id": "Extra2", "group": 11})
graph.links.push({"source": "Extra1", "target": "Valjean", "strength": 1},{"source": "Extra1", "target": "Extra2", "strength": 2})
update()
simulation.restart()
}
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var link, linkEnter, nodeWrapper, nodeWrapperEnter;
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2))
.on("tick", ticked);
var allLinkG = svg.append("g")
.attr("class", "allLinkG")
var allNodeG = svg.append("g")
.attr("class", "allNodeG")
update()
simulation.restart()
function update(){
link = allLinkG
.selectAll("line")
.data(graph.links, function(d){ return d.id })
link.exit().remove()
linkEnter = link
.enter().append("line");
link = linkEnter.merge(link).attr("class","merged");
nodeWrapper = allNodeG
.selectAll("nodeWrapper")
.data(graph.nodes, function(d) { return d.id; })
nodeWrapper.exit().remove();
nodeWrapperEnter = nodeWrapper.enter()
.append("g").attr("class","nodeWrapper")
.append("circle")
.attr("r", 2.5)
nodeWrapper = nodeWrapperEnter
.merge(nodeWrapper).attr("class","merged");
simulation
.nodes(graph.nodes);
simulation.force("link")
.links(graph.links);
}
function ticked() {
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; });
nodeWrapper
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
Thanks very much for any help.
A few issues here:
Your selectAll is selecting by element type nodeWrapper, you meant by class .nodeWrapper.
You change the class name to "merged" after your .merge, you can't do this as it breaks future selections by .nodeWrapper class.
When you .merge your selection, you are merging circles with gs. You should stay consistent and operate on the gs only.
Quick refactor:
function update() {
link = allLinkG
.selectAll("line")
.data(graph.links, function(d) {
return d.id
})
link.exit().remove()
linkEnter = link
.enter().append("line");
link = linkEnter.merge(link).attr("class", "merged");
nodeWrapper = allNodeG
.selectAll(".nodeWrapper") //<-- class nodeWrapper
.data(graph.nodes, function(d) {
return d.id;
})
nodeWrapperEnter = nodeWrapper.enter()
.append("g").attr("class", "nodeWrapper"); //<-- enter selection should be gs
nodeWrapperEnter //<-- append your circles
.append("circle")
.attr("r", 2.5)
nodeWrapper = nodeWrapperEnter //<-- merge, don't change class
.merge(nodeWrapper);
nodeWrapper.exit().remove(); //<-- and the exit
simulation
.nodes(graph.nodes);
simulation.force("link")
.links(graph.links);
}
Note, I also modified your tick function to operate on the g instead of the circle:
nodeWrapper
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
Full code is here.

How to represent this data with D3.js?

I will try to make a resume of my problem and give some additional information if someone have a lead for me.
I want to display the history of an element in an application. How it have been construct and what it have done. So it could have 1+ parents, 1+ brothers and 1+ children. It's like a genealogy tree but it could have 3 parents and parents and children can be mixed to give others children. So I thought about a network, and I ended with this :
http://jsfiddle.net/ggrwc8p6/3/
var data = {
"nodes":[
{"name":"1-STS","group":1},
{"name":"2-STS","group":1},
{"name":"1-ADN","group":2},
{"name":"2-ADN","group":2},
{"name":"3-ADN","group":2},
{"name":"4-ADN","group":2}
],
"links":[
{"source":0,"target":2,"value":1},
{"source":1,"target":2,"value":1},
{"source":2,"target":3,"value":5},
{"source":3,"target":4,"value":5},
{"source":3,"target":5,"value":5}
]
};
var width = 500,
height = 500;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-120)
.linkDistance(120)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
function makeIt(error, graph) {
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", function(d) { return "node " + d.group; })
.attr("r", 15)
.style("fill", function(d) { return color(d.group); })
.call(force.drag);
node.append("title")
.text(function(d) { return d.name; });
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; });
});
}
makeIt("",data);
My problem here are :
How can I make this like a tree ? From top to bottom and not with no hierarchy like here ?
How can I put some text in dot (title) and lines ?
Thank for the people that will help me.
++

D3 Force Layout - subgraph clustering feature

I have some data I am trying to display with the D3 force layout. Apologies if this is a naive question, or if the terminology i employ in the question title is not accurate. I couldn't see an answer quite what i was looking for.
I made a fiddle with a sample showing what I am on about here :
http://jsfiddle.net/stevendwood/f3GJT/8/
In the example I have one node (0) which has lots of links. Another node (16) has a smaller amount of links, 0 and 16 are both connected to 15.
So what i would like is for 0 and 16 to be little clusters with their connected nodes appearing in a nice circle around them.
I vainly tried to customise the charge based on the number of links, but I think what i want to do is somehow make nodes more attracted to nodes they are connected to and less attracted to nodes that they are not connected to.
I would like something like this if possible :
var w = 500,
h = 500,
nodes = [],
links = [];
/* Fake up some data */
for (var i=0; i<20; i++) {
nodes.push({
name: ""+i
});
}
for (i=0; i<16; i++) {
links.push({
source: nodes[i],
target: nodes[0]
});
}
links.push({
source: nodes[16],
target: nodes[15]
});
for (i=17; i<20; i++) {
links.push({
source: nodes[i],
target: nodes[16]
});
}
var countLinks = function(n) {
var count = 0;
links.forEach(function(l) {
if (l.source === n || l.target === n) {
count++;
}
});
return count;
}
/////////////////////////////////////////////
var vis = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h);
var force = d3.layout.force()
.nodes(nodes)
.links([])
.gravity(0.05)
.charge(function(d) {
return countLinks(d) * -50;
})
.linkDistance(300)
.size([w, h]);
var link = vis.selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", "link")
.attr("stroke", "#CCC")
.attr("fill", "none");
var node = vis.selectAll("circle.node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("svg:circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 14)
.style("fill", "#CCC")
.style("stroke", "#AAA")
.style("stroke-width", 1.5)
node.append("text").text(function(d) { return d.name; })
.attr("x", -6)
.attr("y", 6);
force.on("tick", function(e) {
node.attr("transform", function(d, i) {
return "translate(" + d.x + "," + 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; })
});
force.start();
Why did you leave out the links when declaring the force layout? If you add them back in, it looks much closer to what you wanted:
var force = d3.layout.force()
.nodes(nodes)
//.links([])
.links(links)
.gravity(0.1)
.charge(-400)
.linkDistance(75)
.size([w, h]);
http://jsfiddle.net/f3GJT/11/

Categories