D3 JSON文件,源和索引为字符串而不是索引

时间:2022-08-26 23:25:32

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?

此代码与JSON文件完美配合,其中源和索引采用索引的形式。但是,当我切换到源和目标为字符串的格式时,它会抛出TypeError:e [u.source.index]未定义。我该如何克服这个问题?

<!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>

1 个解决方案

#1


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:

你正在使用D3 v3.x.虽然在D3 v4.x中按名称链接是一项简单的任务,但似乎在D3 v3.x中它是不可能的。请参阅D3 v3.x中的此问题,以及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.

注意:source和target属性的值最初可以指定为nodes数组的索引;在调用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

未捕获的TypeError:无法读取未定义的属性'force'

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>

#1


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:

你正在使用D3 v3.x.虽然在D3 v4.x中按名称链接是一项简单的任务,但似乎在D3 v3.x中它是不可能的。请参阅D3 v3.x中的此问题,以及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.

注意:source和target属性的值最初可以指定为nodes数组的索引;在调用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

未捕获的TypeError:无法读取未定义的属性'force'

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>