Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

d3 - "Cannot create property 'vx' on number '65'"

So I'm trying to purpose this great example Force-Directed Graph for some very simple json: https://raw.githubusercontent.com/DealPete/forceDirected/master/countries.json

My work is here: codepen

I'm getting a never-ending feed of errors from d3 with no error at the start to suggest something wrong with my code. It starts like this:

XHR finished loading: GET "https://raw.githubusercontent.com/DealPete/forceDirected/master/countries.json".
[...]
d3.min.js:2 Uncaught Error: missing: 0
    at ar (d3.min.js:2)
    at r (d3.min.js:5)
    at Function.e.links (d3.min.js:5)
    at pen.js:46
    at Object.<anonymous> (d3.min.js:7)
    at d.call (d3.min.js:4)
    at XMLHttpRequest.e (d3.min.js:7)
ar @ d3.min.js:2
r @ d3.min.js:5
e.links @ d3.min.js:5
(anonymous) @ pen.js:46
(anonymous) @ d3.min.js:7
call @ d3.min.js:4
e @ d3.min.js:7
d3.min.js:5 Uncaught TypeError: Cannot create property 'vx' on number '66'
    at e (d3.min.js:5)
    at d3.min.js:5
    at Fe.each (d3.min.js:5)
    at e (d3.min.js:5)
    at n (d3.min.js:5)
    at yn (d3.min.js:2)
    at gn (d3.min.js:2)
e @ d3.min.js:5
(anonymous) @ d3.min.js:5
each @ d3.min.js:5
e @ d3.min.js:5
n @ d3.min.js:5
yn @ d3.min.js:2
gn @ d3.min.js:2
d3.min.js:5 Uncaught TypeError: Cannot create property 'vx' on number '66'
    at e (d3.min.js:5)
    at d3.min.js:5
    at Fe.each (d3.min.js:5)
    at e (d3.min.js:5)
    at n (d3.min.js:5)
    at yn (d3.min.js:2)
    at gn (d3.min.js:2)
e @ d3.min.js:5
(anonymous) @ d3.min.js:5
each @ d3.min.js:5
e @ d3.min.js:5
n @ d3.min.js:5
yn @ d3.min.js:2
gn @ d3.min.js:2

I can't actually find a good introductory resource on force graphs in d3 v4+, so I must just hack at it.

html

<main>
  <section class="d3">
  </section>
</main>

code

const api = 'https://raw.githubusercontent.com/DealPete/forceDirected/master/countries.json' 

let root = d3.select(".d3"),
    width = +root.attr("width"),
    height = +root.attr("height")

let svg = root.append('svg')
    .attr("width", width)
    .attr("height", height)

let color = d3.scaleOrdinal(d3.schemeCategory20);

let simulation = d3.forceSimulation()
    .force("link", d3.forceLink().id((d) => d.country))
    .force("charge", d3.forceManyBody())
    .force("center", d3.forceCenter(width / 2, height / 2));

d3.json(api, function(error, graph) {
  if (error) 
    throw error

  let link = svg.append("g")
    .attr("class", "links")
    .selectAll("line")
    .data(graph.links)
    .enter().append("line")
    .attr("stroke-width",  () => 4);

  let node = svg.append("g")
    .attr("class", "nodes")
    .selectAll("circle")
    .data(graph.nodes)
    .enter().append("circle")
    .attr("r", 5)
    .attr("fill", d => color(1))
    .call(d3.drag()
      .on("start", dragstarted)
      .on("drag", dragged)
      .on("end", dragended))

  simulation
    .nodes(graph.nodes)
    .on("tick", ticked)

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

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

function dragstarted(d) {
  if (!d3.event.active) simulation.alphaTarget(0.3).restart();
  d.fx = d.x;
  d.fy = d.y;
}

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 = null;
  d.fy = null;
}
like image 363
roberto tomás Avatar asked May 03 '17 12:05

roberto tomás


1 Answers

Have a look at your links array:

[
    { "target": 66, "source": 0 },
    { "target": 3, "source": 1 },
    { "target": 100, "source": 2 },
    ...
]

Now have a look at your id function:

.id((d) => d.country)

As you can see, there is no country in your links array.

Therefore, since you are using the numeric index for the links, simply drop the id() function. According to the API:

If id is specified, sets the node id accessor to the specified function and returns this force. If id is not specified, returns the current node id accessor, which defaults to the numeric node.index

Here is your working code:

const api = 'https://raw.githubusercontent.com/DealPete/forceDirected/master/countries.json'

var width = 500,
  height = 500;

let svg = d3.select("body").append('svg')
  .attr("width", width)
  .attr("height", height)

let color = d3.scaleOrdinal(d3.schemeCategory20);

let simulation = d3.forceSimulation()
  .force("link", d3.forceLink())
  .force("charge", d3.forceManyBody())
  .force("center", d3.forceCenter(width / 2, height / 2));

d3.json(api, function(error, graph) {
  if (error)
    throw error

  let link = svg.append("g")
    .attr("class", "links")
    .selectAll("line")
    .data(graph.links)
    .enter().append("line")
    .attr("stroke", "black")
    .attr("stroke-width", 4);

  let node = svg.append("g")
    .attr("class", "nodes")
    .selectAll("circle")
    .data(graph.nodes)
    .enter().append("circle")
    .attr("r", 5)
    .attr("fill", d => color(1))
    .call(d3.drag()
      .on("start", dragstarted)
      .on("drag", dragged)
      .on("end", dragended))

  simulation
    .nodes(graph.nodes)
    .on("tick", ticked)

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

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

function dragstarted(d) {
  if (!d3.event.active) simulation.alphaTarget(0.3).restart();
  d.fx = d.x;
  d.fy = d.y;
}

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 = null;
  d.fy = null;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
like image 69
Gerardo Furtado Avatar answered Nov 16 '22 03:11

Gerardo Furtado