我想使用d3构建力导向图。图表的每个节点都是来自漫威漫画(例如蜘蛛侠)的角色。如果角色出现在一个漫画中,则表示它们的节点之间应该存在链接。
我有一个characters_by_comics.json
文件,其中包含以下类型的对象数组:
{
"comic_id": 2,
"characters": [
1009220,
1010776,
1009378,
1010363,
1009215,
1009471,
1009718
]
},
我还有characters.json
,其中包含有关每个角色的所有信息,包括character_id
。此外,comics.json
包含每个漫画的标题及其comic_id
。
图表将变得非常安静。这将以负面的方式影响用户体验。因此,为了防止这种情况,我添加了有关每个字符的一些信息,例如race
,gender
,alive
等。使用此信息,我计划:
only female
过滤器,则只应显示代表女性角色的节点appear in one comic
,还包括belongs to one team
。我的问题是如何转换我拥有的数据,以便使用d3轻松创建nodes
和links
?
答案 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>