I was learning how to created linked force charts for data that had was charted by name (as in Mike Bostocks example here: https://bl.ocks.org/mbostock/4062045) and data that was only mapped by index value. The first is preferable because the links are directly attached to the nodes by their names, rather than by their index values, which could change if the data is changed or remapped.
xxxxxxxxxx
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.12.0/d3.min.js"></script>
<style>
svg {
position: relative;
display: block;
float: left;
clear: both;
}
.links line {
stroke: darkgrey;
stroke-opacity: .9;
}
.nodes circle {
stroke: black;
stroke-width: 1px;
}
button {
position: relative;
display: block;
float: left;
margin-top: 20px;
margin-left: 200px;
}
</style>
<body>
<button type="button" name="changeType" value="changetoNonId" onclick="nonId();">Change to ID</button>
</body>
<script>
// Define SVG, colorScale, height, width, and initialize simulation.
var width = 750;
var height = 500;
var svg = d3.select("body").append("svg").attr("width",width).attr("height",height)
var colorScale = d3.scaleOrdinal(d3.schemeCategory20);
// Ingest our data.
d3.json("miserables.json", function(error, data){
if(error) throw error;
console.log(data)
// Define a simulation, attach the forces, and finally attach the nodes. Add an "ontick" event
// listener to the nodes. This will fire the update, 'ticked' function.
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.name; }))
// The "id" method returns the "name" of each node, rather than the id. This is important, because our links are not indexed based, but have the source and target of the relevant nodes. If our links data were like this: [{"source": 0, "target": 1},{"source": 0, "target": 2}] then using the id method would not be needed, so long as our nodes were kept in the same index order.
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2))
simulation // We're separating the simulation (d3.forceSimulation) here, to allow us to bind both our nodes to it, and to later select specific forces (as we do below.)
.nodes(data.nodes)
.on("tick", ticked)
simulation.force("link") // here we're re-selecting the "link" force. We can't include it all in one dot-chained method because then the other forces wouldn't be attached to d3.forceSimulation.
.links(data.links)
// Create our link and nodes. It isn't necessary to assign them initial x and y values, which are assigned in our "ticked" function.
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(data.links)
.enter().append("line")
.attr("stroke-width", function(d){ return Math.sqrt(d.value)})
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(data.nodes)
.enter().append("circle")
.attr("r", 5)
.attr("fill", function(d){ return colorScale(d.group)})
node.append("title")
.text(function(d){ return d.id})
function ticked(){
link
.attr("x1", function(d){return d.source.x;})
.attr("x2", function(d){return d.target.x;})
.attr("y1", function(d){return d.source.y;})
.attr("y2", function(d){return d.target.y;})
node
.attr("cx", function(d){return d.x})
.attr("cy", function(d){return d.y})
}
})
var nonId = function() {
var width = 400, height = 300
d3.selectAll("circle").remove();
d3.select("button").remove();
d3.json("data2.json", function(error, data){
if(error) throw error;
var nodes = data.nodes;
var links = data.links;
var simulation = d3.forceSimulation(nodes)
// charge defaults to -30 strength, we need to increase it
.force('charge', d3.forceManyBody().strength(-100))
// Pull to center of svg
.force('center', d3.forceCenter(width / 2, height / 2))
// Link pulls together a list of links, according to their source and target information.
.force('link', d3.forceLink().links(links))
.on('tick', ticked);
simulation.restart();
function updateLinks() {
var u = d3.select('.links')
.selectAll('line')
.data(links)
u.enter()
.append('line')
.merge(u)
.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
})
u.exit().remove()
}
function updateNodes() {
u = d3.select('.nodes')
.selectAll('text')
.data(nodes)
u.enter()
.append('text')
.text(function(d) {
return d.id
})
.merge(u)
.attr('x', function(d) {
return d.x
})
.attr('y', function(d) {
return d.y
})
.attr('dy', function(d) {
return 5
})
u.exit().remove()
}
function ticked() {
updateLinks()
updateNodes()
}
});
}
</script>
https://cdnjs.cloudflare.com/ajax/libs/d3/4.12.0/d3.min.js