1

This code works perfectly with a JSON file where the source and index are in the form of indices. However, when I switch to a format with the source and target as strings, it throws up TypeError: e[u.source.index] is undefined. How do I overcome this?

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>

var width = 960,
    height = 500,
    active = d3.select(null);

var zoom = d3.behavior.zoom()
    .scaleExtent([1, 8])
    .on("zoom", zoomed);    

var force = d3.layout.force()
    .size([width, height])
    .charge(-400)
    .linkDistance(40)
    .on("tick", tick);

var drag = force.drag()
    .on("dragstart", dragstart);

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

var g = svg.append("g");

var link = g.selectAll(".link"),
    node = g.selectAll(".node");

svg
    .call(zoom) // delete this line to disable free zooming
    .call(zoom.event);

d3.json("data/miserables.json", function(error, graph) {
  if (error) throw error;

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

  link = link.data(graph.links)
    .enter().append("line")
      .attr("class", "links")
      .style("stroke", "#999");

  node = node.data(graph.nodes)
    .enter().append("circle")
      .attr("class", "node")
      .attr("r", 12)
      .on("click", clicked)
      //.call(drag);
});

function tick() {
  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; });
}

function clicked(d){
  if (active.node() === this) return reset();
  active.classed("active", false);
  active = d3.select(this).classed("active", true);

  var bbox = active.node().getBBox(),
      bounds = [[bbox.x, bbox.y],[bbox.x + bbox.width, bbox.y + bbox.height]];

  var dx = bounds[1][0] - bounds[0][0],
      dy = bounds[1][1] - bounds[0][1],
      x = (bounds[0][0] + bounds[1][0]) / 2,
      y = (bounds[0][1] + bounds[1][1]) / 2,
      scale = Math.max(1, Math.min(8, 0.9 / Math.max(dx / width, dy / height))),
      translate = [width / 2 - scale * x, height / 2 - scale * y];

  svg.transition()
      .duration(750)
      .call(zoom.translate(translate).scale(scale).event);
} 

function reset() {
  active.classed("active", false);
  active = d3.select(null);

  svg.transition()
      .duration(750)
      .call(zoom.translate([0, 0]).scale(1).event);
}    


function dragstart(d) {
  d3.select(this).classed("fixed", d.fixed = true);
}

function zoomed() {
  console.log(d3.event)
  g.style("stroke-width", 1.5 / d3.event.scale + "px");
  g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}    

</script>
VerletIntegrator
  • 159
  • 2
  • 10

1 Answers1

1

You're using D3 v3.x. While linking by name is an easy task in D3 v4.x, it seems that it's not possible in D3 v3.x. See this issue in D3 v3.x, and this explanation in the API:

Note: the values of the source and target attributes may be initially specified as indexes into the nodes array; these will be replaced by references after the call to start.

Thus, the snippet below won't work (the code is not mine, I just found it online and changed the links array from indices to names):

var nodes = [{
    name: "node1"
}, {
    name: "node2"
}, {
    name: "node3"
}, {
    name: "node4"
}, {
    name: "node5"
}, {
    name: "node6"
}, {
    name: "node7"
}];

var edges = [{
    source: "node1",
    target: "node3"
}, {
    source: "node1",
    target: "node2"
}, {
    source: "node1",
    target: "node4"
}, {
    source: "node2",
    target: "node3"
}, {
    source: "node2",
    target: "node5"
}, {
    source: "node2",
    target: "node6"
}, {
    source: "node3",
    target: "node"
}];

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

var force = d3.layout.force()
    .nodes(nodes)
    .links(edges)
    .size([width, height])
    .linkDistance(150)
    .charge(-400);

force.start();
var svg_edges = svg.selectAll("line")
    .data(edges)
    .enter()
    .append("line")
    .style("stroke", "#ccc")
    .style("stroke-width", 1);

var color = d3.scale.category20();
var svg_nodes = svg.selectAll("circle")
    .data(nodes)
    .enter()
    .append("circle")
    .attr("r", 20)
    .style("fill", function(d, i) {
        return color(i);
    })
    .call(force.drag);
var svg_texts = svg.selectAll("text")
    .data(nodes)
    .enter()
    .append("text")
    .style("fill", "black")
    .attr("dx", 20)
    .attr("dy", 8)
    .text(function(d) {
        return d.name;
    });

force.on("tick", function() {
    svg_edges.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;
        });

    svg_nodes.attr("cx", function(d) {
            return d.x;
        })
        .attr("cy", function(d) {
            return d.y;
        });

    svg_texts.attr("x", function(d) {
            return d.x;
        })
        .attr("y", function(d) {
            return d.y;
        });
});
<script src="https://d3js.org/d3.v4.min.js"></script>

If you click "run snippet", you'll only see an error:

Uncaught TypeError: Cannot read property 'force' of undefined

Solution: Keep your links array with indices. However, if you already have/receive an array with names, you can change it to indices:

var nodeByName = d3.map(nodes, function(d) {
    return d.name;
});

edges.forEach(function(d) {
    d.source = nodeByName.get(d.source);
    d.target = nodeByName.get(d.target);
});

Here is the same code of the first snippet with the above-mentioned changes. Now it works:

var nodes = [{
    name: "node1"
}, {
    name: "node2"
}, {
    name: "node3"
}, {
    name: "node4"
}, {
    name: "node5"
}, {
    name: "node6"
}, {
    name: "node7"
}];

var edges = [{
    source: "node1",
    target: "node3"
}, {
    source: "node1",
    target: "node2"
}, {
    source: "node1",
    target: "node4"
}, {
    source: "node2",
    target: "node3"
}, {
    source: "node2",
    target: "node5"
}, {
    source: "node2",
    target: "node6"
}, {
    source: "node2",
    target: "node7"
}];

var nodeByName = d3.map(nodes, function(d) {
    return d.name;
});

edges.forEach(function(d) {
    d.source = nodeByName.get(d.source);
    d.target = nodeByName.get(d.target);
});

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

var force = d3.layout.force()
    .nodes(nodes)
    .links(edges)
    .size([width, height])
    .linkDistance(150)
    .charge(-400);

force.start();
var svg_edges = svg.selectAll("line")
    .data(edges)
    .enter()
    .append("line")
    .style("stroke", "#ccc")
    .style("stroke-width", 1);

var color = d3.scale.category20();
var svg_nodes = svg.selectAll("circle")
    .data(nodes)
    .enter()
    .append("circle")
    .attr("r", 20)
    .style("fill", function(d, i) {
        return color(i);
    })
    .call(force.drag);
var svg_texts = svg.selectAll("text")
    .data(nodes)
    .enter()
    .append("text")
    .style("fill", "black")
    .attr("dx", 20)
    .attr("dy", 8)
    .text(function(d) {
        return d.name;
    });

force.on("tick", function() {
    svg_edges.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;
        });

    svg_nodes.attr("cx", function(d) {
            return d.x;
        })
        .attr("cy", function(d) {
            return d.y;
        });

    svg_texts.attr("x", function(d) {
            return d.x;
        })
        .attr("y", function(d) {
            return d.y;
        });
});
<script src="https://d3js.org/d3.v3.min.js"></script>
Gerardo Furtado
  • 100,839
  • 9
  • 121
  • 171