Question

I'm new to javascript and D3.js, and I am trying to understand how it all works. I have been playing with the force-directed graph example here: http://bl.ocks.org/mbostock/4062045

What I want to do, is to change the JSON links from being array numbers to node names. I'm trying to visualize a small network topology, and I have the node neighbors all set up. Here is the JSON data I would like to use:

{
  "nodes":[
    {"name":"stkbl0001","group":1},
    {"name":"stkbl0002","group":1},
    {"name":"stkbl0003","group":1},
    {"name":"stkbl0004","group":1},
    {"name":"stkbl0005","group":1}
  ],
  "links":[
    {"source":"stkbl0001","target":"stkbl0005","value":3},
    {"source":"stkbl0002","target":"stkbl0005","value":3},
    {"source":"stkbl0003","target":"stkbl0005","value":3},
    {"source":"stkbl0004","target":"stkbl0005","value":3}
  ]

I really don't know how to alter the D3 code to tie all this together. I fail to see the section where the array numbers is fetched and glued together as links. This is probably a stupid question, but it will help me a lot!

EDIT:

Here is the javascript code I have so far based on the input from Lars Kotthoff:

<!DOCTYPE html>
<meta charset="utf-8">
<style>

.node {
  stroke: #fff;
  stroke-width: 1.5px;
}

.link {
  stroke: #999;
  stroke-opacity: .6;
}

</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>

var width = 960,
    height = 500;

var color = d3.scale.category20();

var force = d3.layout.force()
    .charge(-120)
    .linkDistance(30)
    .size([width, height]);

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

d3.json("mini.json", function(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", "node")
      .attr("r", 5)
      .style("fill", function(d) { return color(d.group); })
      .call(force.drag);

  var nodeMap = {};
  nodes.forEach(function(d) { nodeMap[d.name] = d; });

  links.forEach(function(l) {
      l.source = nodeMap[l.source];
      l.target = nodeMap[l.target];
  })

  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; });
  });
});
</script>

This fails at line 55 (nodes.forEach(function(d) { nodeMap[d.name] = d; });) with the error:

Uncaught ReferenceError: nodes is not defined
Was it helpful?

Solution

This link links to a working example based on the your example.

The critical code is placed just before initialization of force layout:

var nodeMap = {};

graph.nodes.forEach(function(d) { nodeMap[d.name] = d; });

graph.links.forEach(function(l) {
    l.source = nodeMap[l.source];
    l.target = nodeMap[l.target];
})

force.nodes(graph.nodes)
    .links(graph.links)
    .start();

That way you will be able to use your data format in the same fashion as the original format is used (and many examples on the net follow that original format, so you will be able to adapt many of them to your format without problems).

(json file is not used in my example, due to restrictions of jsfiddle; instead, function getData() is made to return the data; but this is not essential to your question; you can use this solution with json files too)

Hope this helps.

OTHER TIPS

D3 provides two ways of specifying link source and target for the force layout. The first, used in the example you've linked to, is to provide the index of the node in the array of nodes. When the force layout is started, this is replaced with the reference to the actual node. The second is to provide the reference to the actual node explicitly.

To reference a node by name, you need something that allows you to resolve that reference. For example:

var nodeMap = {};
graph.nodes.forEach(function(d) { nodeMap[d.name] = d; });

Then you can do

graph.links.forEach(function(l) {
  l.source = nodeMap[l.source];
  l.target = nodeMap[l.target];
})

You can of course also use this to define links to start with:

"links":[
 {"source":nodeMap["stkbl0001"],"target":nodeMap["stkbl0005"],"value":3}
]
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top