适用于d3节点的数据结构

时间:2018-02-10 17:27:27

标签: json d3.js

我想使用d3构建力导向图。图表的每个节点都是来自漫威漫画(例如蜘蛛侠)的角色。如果角色出现在一个漫画中,则表示它们的节点之间应该存在链接。

我有一个characters_by_comics.json文件,其中包含以下类型的对象数组:

  {
    "comic_id": 2,
    "characters": [
      1009220,
      1010776,
      1009378,
      1010363,
      1009215,
      1009471,
      1009718
    ]
  },

我还有characters.json,其中包含有关每个角色的所有信息,包括character_id。此外,comics.json包含每个漫画的标题及其comic_id

图表将变得非常安静。这将以负面的方式影响用户体验。因此,为了防止这种情况,我添加了有关每个字符的一些信息,例如racegenderalive等。使用此信息,我计划:

  1. 创建可应用于图表的不同过滤器。例如,如果我应用only female过滤器,则只应显示代表女性角色的节点
  2. 创建不同类型的链接:不仅是appear in one comic,还包括belongs to one team
  3. 我的问题是如何转换我拥有的数据,以便使用d3轻松创建nodeslinks

1 个答案:

答案 0 :(得分:0)

您没有包含characters.json的摘要,但这是您的节点 data。听起来它可能已经正确格式化了。它需要是这样的:

var characters = [
  {
    id: 1009220,
    name: 'Ms Marvel'
  }, {
    id: 1010776,
    name: 'Spiderman'
  }
  ....
];

现在真正的问题是,我们如何从您的characters_by_comics.json获取链接数据?假设有这样的结构:

var charactersByComic = [{
  "comic_id": 2,
  "characters": [
    1009221,
    1010776,
    ...
  ]
}, {
  "comic_id": 3,
  "characters": [
    1009221,
    1009220,
    1009379,
    ...
  ]
}];

你可以这样做:

var linkData = {}; // object to hold data
charactersByComic.forEach(d0 => { // loop the master data
  d0.characters.forEach(d1 => { // outer loop of characters
    d0.characters.forEach(d2 => { // inner loop of characters
      var key = d1 + "|" + d2; // create unique key to prevent duplicate relationshipts
      if (d1 !== d2 && !linkData[key]) { // if its not the same character, and they don't already have a relationship
        linkData[key] = { // add it
          source: d1,
          target: d2
        };
      }
    });
  });
});
linkData = Object.values(linkData); // take just the array of relationships

这会产生我们想要的链接 structure

[
  {"source":1009221,"target":1010776},
  {"source":1009221,"target":1009378},
  ...
]

现在我们可以将整个事情塞进一个力导向图:

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

  <style>
    .links line {
      stroke: #999;
      stroke-opacity: 0.6;
    }
    
    .nodes circle {
      stroke: #fff;
      stroke-width: 1.5px;
    }
  </style>
  <script src="https://d3js.org/d3.v4.min.js"></script>


  <svg width="200" height="200"></svg>

  <script>
    var characters = [{
      id: 1009220,
      name: 'Ms Marvel'
    }, {
      id: 1010776,
      name: 'Spiderman'
    }, {
      id: 1009378
    }, {
      id: 1010363
    }, {
      id: 1009215
    }, {
      id: 1009471
    }, {
      id: 1009718
    }, {
      id: 1009221
    }, {
      id: 1010777
    }, {
      id: 1009379
    }, {
      id: 1010361
    }, {
      id: 1009212
    }, {
      id: 1009474
    }, {
      id: 1009715
    }];
    
    var charactersByComic = [{
      "comic_id": 2,
      "characters": [
        1009221,
        1010776,
        1009378,
        1010363,
        1009215,
        1009471,
        1009718,
        1010777
      ]
    }, {
      "comic_id": 3,
      "characters": [
        1009221,
        1009220,
        1009379,
        1010361,
        1009212,
        1009474,
        1009715,
        1010777
      ]
    }];
    
    var linkData = {};
    charactersByComic.forEach(d0 => {
      d0.characters.forEach(d1 => {
        d0.characters.forEach(d2 => {
          var key = d1 + "|" + d2;
          if (d1 !== d2 && !linkData[key]) {
            linkData[key] = {
              source: d1,
              target: d2
            };
          }
        });
      });
    });
    linkData = Object.values(linkData);  
   
    var svg = d3.select("svg"),
      width = +svg.attr("width"),
      height = +svg.attr("height");


    var simulation = d3.forceSimulation()
      .force("link", d3.forceLink().id(function(d) {
        console.log(d)
        return d.id;
      }))
      .force("charge", d3.forceManyBody())
      .force("center", d3.forceCenter(width / 2, height / 2));

      var link = svg.append("g")
        .attr("class", "links")
        .selectAll("line")
        .data(linkData)
        .enter().append("line")
        .attr("stroke-width", 1);

      var node = svg.append("g")
        .attr("class", "nodes")
        .selectAll("circle")
        .data(characters)
        .enter()
        .append("circle")
        .attr("r", 5)
        .attr("fill", "steelblue");
        
      node.append("title")
      .text(function(d) { return d.id; });

      simulation
       .nodes(characters)
        .on("tick", ticked);


      simulation.force("link")
        .links(linkData);
      
      console.log(simulation)

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

  </script>