无法将标签添加到 d3js 强制布局网络可视化

Posted

技术标签:

【中文标题】无法将标签添加到 d3js 强制布局网络可视化【英文标题】:Can't add labels to d3js force layout network visualization 【发布时间】:2014-05-13 18:26:56 【问题描述】:

我一直在努力尝试使用 d3js 在强制导向布局网络可视化中实现标签(我正在使用 Python 和 Flask 从 API 生成 JSON 文件并在网页中呈现可视化。

我看过很多例子,比如...

http://bl.ocks.org/mbostock/2706022

http://bl.ocks.org/mbostock/1153292

http://bl.ocks.org/MoritzStefaner/1377729

...但我对 javascript 还很陌生,所以我很难将这些代码重新实现到我当前的代码中:

我一直使用的 JSON 文件是:

"directed": true, "graph": [], "nodes": ["id": "user1", "id": "user2", "id": "user3", "id": "user4", "id": "user5", "id": "user6", "id": "user7", "id": "user8", "id": "user9", "id": "user10", "id": "user11", "id": "user12", "id": "user13", "id": "user14", "id": "user15", "id": "user16", "id": "user17", "id": "user18", "id": "user19", "id": "user20", "id": "user21", "id": "user22", "id": "myaccount", "id": "user23", "id": "user24", "id": "user25", "id": "user26", "id": "user27", "id": "user28", "id": "user29", "id": "user30", "id": "user31", "id": "user32", "id": "user33", "id": "user34", "id": "user35", "id": "user36", "id": "user37", "id": "user38", "id": "user39", "id": "user40", "id": "user41", "id": "user42", "id": "user43", "id": "user44", "id": "user45", "id": "user46", "id": "user47", "id": "user48", "id": "user49", "id": "user50", "id": "user51", "id": "user52", "id": "user53", "id": "user54", "id": "user55", "id": "user56", "id": "user57", "id": "user58", "id": "user59", "id": "user60", "id": "user61", "id": "user62", "id": "user63", "id": "user64", "id": "user65", "id": "user66", "id": "user67", "id": "user68", "id": "user69", "id": "user70", "id": "user71", "id": "user72", "id": "user73", "id": "user74", "id": "user75", "id": "user76", "id": "user77", "id": "user78", "id": "user79", "id": "user80", "id": "user81", "id": "user82", "id": "user83", "id": "user84", "id": "user85", "id": "user86", "id": "user87", "id": "user88", "id": "user89", "id": "user90", "id": "user91", "id": "user92", "id": "user93", "id": "user94", "id": "user95", "id": "user96", "id": "user97", "id": "user98", "id": "user99", "id": "user100", "id": "user101", "id": "user102", "id": "cuser103", "id": "user104", "id": "user105", "id": "user106", "id": "user107", "id": "user108", "id": "user109", "id": "user110", "id": "user111", "id": "user112", "id": "user113", "id": "user114", "id": "user115", "id": "user116", "id": "user117", "id": "user118", "id": "user119", "id": "user120", "id": "user121", "id": "user122", "id": "user123", "id": "user124", "id": "user125", "id": "user126", "id": "user127", "id": "user128", "id": "user129", "id": "user130", "id": "user131"], "links": ["source": 22, "target": 72, "weight": 1, "source": 22, "target": 65, "weight": 3, "source": 22, "target": 115, "weight": 1, "source": 22, "target": 56, "weight": 1, "source": 22, "target": 106, "weight": 1, "source": 22, "target": 124, "weight": 3, "source": 22, "target": 48, "weight": 1, "source": 22, "target": 101, "weight": 1, "source": 22, "target": 111, "weight": 1, "source": 22, "target": 64, "weight": 4, "source": 22, "target": 112, "weight": 1, "source": 22, "target": 71, "weight": 1, "source": 48, "target": 121, "weight": 1, "source": 48, "target": 114, "weight": 1, "source": 48, "target": 34, "weight": 1, "source": 48, "target": 28, "weight": 1, "source": 48, "target": 43, "weight": 1, "source": 48, "target": 52, "weight": 1, "source": 48, "target": 64, "weight": 1, "source": 56, "target": 101, "weight": 1, "source": 56, "target": 9, "weight": 1, "source": 56, "target": 50, "weight": 1, "source": 56, "target": 85, "weight": 1, "source": 64, "target": 51, "weight": 1, "source": 64, "target": 46, "weight": 1, "source": 64, "target": 104, "weight": 1, "source": 64, "target": 122, "weight": 1, "source": 64, "target": 20, "weight": 2, "source": 64, "target": 2, "weight": 1, "source": 64, "target": 23, "weight": 1, "source": 64, "target": 94, "weight": 1, "source": 64, "target": 60, "weight": 1, "source": 64, "target": 6, "weight": 1, "source": 64, "target": 89, "weight": 1, "source": 64, "target": 96, "weight": 1, "source": 64, "target": 131, "weight": 3, "source": 64, "target": 50, "weight": 1, "source": 65, "target": 73, "weight": 2, "source": 65, "target": 90, "weight": 1, "source": 65, "target": 11, "weight": 3, "source": 65, "target": 1, "weight": 3, "source": 65, "target": 29, "weight": 4, "source": 65, "target": 13, "weight": 1, "source": 65, "target": 83, "weight": 1, "source": 65, "target": 78, "weight": 1, "source": 65, "target": 88, "weight": 1, "source": 65, "target": 113, "weight": 2, "source": 65, "target": 63, "weight": 1, "source": 71, "target": 10, "weight": 1, "source": 72, "target": 98, "weight": 3, "source": 72, "target": 123, "weight": 1, "source": 72, "target": 91, "weight": 1, "source": 72, "target": 12, "weight": 1, "source": 72, "target": 107, "weight": 1, "source": 72, "target": 7, "weight": 2, "source": 72, "target": 36, "weight": 4, "source": 72, "target": 128, "weight": 4, "source": 72, "target": 129, "weight": 1, "source": 72, "target": 38, "weight": 2, "source": 101, "target": 69, "weight": 1, "source": 101, "target": 119, "weight": 1, "source": 101, "target": 82, "weight": 1, "source": 101, "target": 21, "weight": 1, "source": 101, "target": 116, "weight": 1, "source": 101, "target": 125, "weight": 1, "source": 101, "target": 4, "weight": 1, "source": 101, "target": 109, "weight": 1, "source": 101, "target": 64, "weight": 2, "source": 101, "target": 53, "weight": 1, "source": 101, "target": 130, "weight": 1, "source": 101, "target": 102, "weight": 1, "source": 101, "target": 32, "weight": 1, "source": 101, "target": 80, "weight": 1, "source": 101, "target": 62, "weight": 1, "source": 106, "target": 103, "weight": 1, "source": 106, "target": 84, "weight": 1, "source": 106, "target": 66, "weight": 1, "source": 106, "target": 67, "weight": 1, "source": 106, "target": 87, "weight": 1, "source": 106, "target": 15, "weight": 1, "source": 106, "target": 126, "weight": 2, "source": 106, "target": 74, "weight": 1, "source": 106, "target": 127, "weight": 1, "source": 106, "target": 42, "weight": 4, "source": 106, "target": 5, "weight": 1, "source": 106, "target": 77, "weight": 2, "source": 106, "target": 79, "weight": 1, "source": 106, "target": 118, "weight": 1, "source": 111, "target": 92, "weight": 1, "source": 111, "target": 54, "weight": 1, "source": 111, "target": 55, "weight": 1, "source": 111, "target": 95, "weight": 1, "source": 111, "target": 41, "weight": 2, "source": 111, "target": 3, "weight": 1, "source": 111, "target": 93, "weight": 3, "source": 111, "target": 76, "weight": 1, "source": 111, "target": 49, "weight": 1, "source": 111, "target": 99, "weight": 1, "source": 111, "target": 44, "weight": 1, "source": 111, "target": 61, "weight": 1, "source": 111, "target": 117, "weight": 2, "source": 111, "target": 81, "weight": 1, "source": 111, "target": 97, "weight": 1, "source": 112, "target": 26, "weight": 1, "source": 112, "target": 8, "weight": 1, "source": 112, "target": 39, "weight": 1, "source": 112, "target": 45, "weight": 1, "source": 112, "target": 57, "weight": 1, "source": 112, "target": 47, "weight": 1, "source": 112, "target": 108, "weight": 1, "source": 112, "target": 37, "weight": 5, "source": 112, "target": 17, "weight": 1, "source": 112, "target": 31, "weight": 1, "source": 112, "target": 131, "weight": 4, "source": 112, "target": 94, "weight": 1, "source": 115, "target": 18, "weight": 3, "source": 115, "target": 19, "weight": 1, "source": 115, "target": 33, "weight": 1, "source": 115, "target": 30, "weight": 1, "source": 115, "target": 110, "weight": 1, "source": 115, "target": 70, "weight": 1, "source": 124, "target": 120, "weight": 1, "source": 124, "target": 0, "weight": 1, "source": 124, "target": 40, "weight": 1, "source": 124, "target": 105, "weight": 1, "source": 124, "target": 35, "weight": 1, "source": 124, "target": 27, "weight": 1, "source": 124, "target": 86, "weight": 3, "source": 124, "target": 68, "weight": 1, "source": 124, "target": 14, "weight": 1, "source": 124, "target": 58, "weight": 1, "source": 124, "target": 24, "weight": 1, "source": 124, "target": 25, "weight": 1, "source": 124, "target": 100, "weight": 1, "source": 124, "target": 59, "weight": 1, "source": 124, "target": 75, "weight": 2, "source": 124, "target": 16, "weight": 2], "multigraph": false

而当前生成不带标签的可视化的代码是:

var width = 600,
    height = 500;

var color = d3.scale.category20();

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

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

d3.json("static/reblognetwork_ session_username .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);

  node.append("text")
    // .attr("x", 12)
    // .attr("dy", ".35em")
    .text(function(d)  return d.id; );

  // var text = svg.append("g").selectAll("text")
  //   .data(graph.nodes)
  // .enter().append("text")
  //   // .attr("x", ".5em")
  //   // .attr("y", ".31em")
  //   .text(function(d)  return d.id; );

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

  );
);

您也可以看到一些我尝试过但没有成功的(注释掉的)代码,这些代码基于之前的问题,例如D3.js, force-graph, cannot display text/label of nodes

任何帮助表示赞赏!


解决方案:下面的最终代码有效。

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

<script>
var width = 500, height = 450;

var svg = d3.select("div").append("svg");

var color = d3.scale.category20();

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

svg.attr("width", width)
    .attr("height", height);

d3.json("static/reblognetwork_ session_username .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("g.node")
      .data(graph.nodes);
  var nodeEnter = node.enter()
      .append("g")
      .attr("class", "node")
      .call(force.drag);
  nodeEnter.append("circle")
      .attr("r", 5)
      .style("fill", function(d)  return color(d.group); )
      .call(force.drag);

  nodeEnter.append("text")
     .attr("dx", 5)
     .style("text-anchor", "start")
     .text(function(d)  return d.id; );

  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("transform", function(d) 
      return "translate(" + [d.x, d.y] + ")";
    );
  );
;


</script>

<div id="networkgraph"></div>

【问题讨论】:

【参考方案1】:

我不擅长故障排除,但我将您的代码插入到 JSFiddle 中,并且节点似乎呈现(虽然不是链接)。

也许它与您传入的数据有关?在这种情况下,我插入 Les Miserables 数据进行测试(所以我所做的唯一更改是删除 d3.json 调用并缩小节点文本的大小)。

http://jsfiddle.net/JA4kD/1/

var width = 500,
    height = 450;

var svg = d3.select("#networkgraph").append("svg");

var color = d3.scale.category20();

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

svg.attr("width", width)
    .attr("height", height);

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("g.node")
    .data(graph.nodes);

var nodeEnter = node.enter()
    .append("g")
    .attr("class", "node")
    .call(force.drag);

nodeEnter.append("circle")
    .attr("r", 5)
    .style("fill", function (d)  return color(d.group); )
    .call(force.drag);

nodeEnter.append("text")
    .attr("dx", 5)
    .style("text-anchor", "start")
    .style("font-size", "8px")
    .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("transform", function (d)  return "translate(" + [d.x, d.y] + ")"; );
);

【讨论】:

【参考方案2】:

当为一个节点添加多个元素时,通常最好有一个 g 元素,其位置由您设置并包含所有其他元素,就像您链接到的第一个示例一样。在您的情况下,这可能如下所示。

var node = svg.selectAll("g.node")
  .data(graph.nodes);
var nodeEnter = node.enter()
  .append("g")
  .attr("class", "node")
  .call(force.drag);
nodeEnter.append("circle")
  .attr("r", 5)
  .style("fill", function(d)  return color(d.group); )
  .call(force.drag);
nodeEnter.append("text")
 .attr("dx", 5)
 .style("text-anchor", "start")
 .text(function(d)  return d.id; );

完整的演示here。

【讨论】:

尝试添加上面的代码,但我在之前的一些代码中遇到了同样的问题,标签(这次还有圆圈)悬停在左上角。 screenshot 你看过完整的演示了吗? 是的。划掉最后一条评论;我重新实现了整个代码,只更改了第 25 行 –– d3.json("static/reblognetwork_ session_username .json", function(error, graph) –– 现在甚至没有生成可视化。不确定如果我调用 JSON 的方式有问题,或者...? 好的,您仍然收到该错误吗?恐怕没有看到您的代码,我无法帮助您。 听起来像是加载 JSON 的问题。你检查它是否加载正确。

以上是关于无法将标签添加到 d3js 强制布局网络可视化的主要内容,如果未能解决你的问题,请参考以下文章

d3js 在地图上强制布局

D3js:自动放置标签以避免重叠? (强制排斥)

d3.js 具有固定布局的绘图网络

网站从开发到上线

饼图标签布局方案

向 Hobbelt 的“Group/Bundle Nodes”D3 强制布局示例中的节点添加标签?