跟进我之前的问题:
JS / d3.js - Removing/merging duplicate links in d3.js node graph
我想问一下是否有办法动态地将组分配给通过边连接在一起的节点。
这是因为我想用相同的颜色为每个簇中的节点着色,源/目标数据由外部数据集提供。
以下是源/目标数据示例:
source: S001A, target: S002A
source: S001A, target: S003A
source: S001A, target: S004A
source: S002A, target: S005A
source: S003A, target: S006A
这是代码的节点创建部分:
node = node
.data(nodeD)
.enter().append("circle")
.attr("class", "node")
.attr("r", 6)
.style("fill", function(d) { return colors(/*Code here..*/); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
我的nodeD数据结构包含我的节点数据,示例如下所示:
{
"NRIC": "S001A",
"name": "Benjamin",
"blk": 123,
"estate": "Woods",
"street": "Woods Street 80",
"unitNo": "12-98",
"postal": 123456,
"school": "Nanyang Technological University",
"Friends": [
"S002A",
"S003A",
"S004A",
]
}
有没有办法根据源/目标数据为节点的同一“组”或集群中的节点着色?或者我是否需要在节点的数据结构中引入一个新属性来将它们组合在一起?
答案 0 :(得分:0)
我认为 cluster 是指链接在一起的节点,没有任何指向另一个集群的链接。
因此,如果是这种情况,在下面的力图中我们有2个集群:
var width = 300;
var height = 200;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var nodes = [{
"id": "foo"
}, {
"id": "bar"
}, {
"id": "baz"
}, {
"id": "foobar"
}, {
"id": "foobaz"
}, {
"id": "barbar"
}, {
"id": "barbaz"
}];
var edges = [{
"source": 0,
"target": 1
}, {
"source": 0,
"target": 2
}, {
"source": 0,
"target": 3
}, {
"source": 1,
"target": 3
}, {
"source": 4,
"target": 5
}, {
"source": 4,
"target": 6
}, {
"source": 5,
"target": 6
}];
var force = d3.forceSimulation()
.force("link", d3.forceLink()
.id(function(d, i) {
return i
}))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
var myedges = svg.selectAll("line")
.data(edges)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 1);
var color = d3.scaleOrdinal(d3.schemeCategory10);
var mynodes = svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("r", 10)
.style("fill", "#888")
.call(d3.drag());
force.nodes(nodes);
force.force("link")
.links(edges);
force.on("tick", function() {
myedges.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;
})
mynodes.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
});
<script src="https://d3js.org/d3.v4.min.js"></script>
如何通过群集为节点着色?这是我的解决方案:
首先,按源和目标对您的edges
或links
数组进行排序。一旦排序,您可以使用以下功能:
var cluster = [
[edges[0].source, edges[0].target]
];
var found;
edges.forEach(d => {
found = 0;
for (var i = 0; i < cluster.length; i++) {
if (cluster[i].indexOf(d.source) > -1 && cluster[i].indexOf(d.target) < 0) {
cluster[i].push(d.target);
found = 1;
} else if (cluster[i].indexOf(d.target) > -1 && cluster[i].indexOf(d.source) < 0) {
cluster[i].push(d.source);
found = 1;
} else if (cluster[i].indexOf(d.target) > -1 && cluster[i].indexOf(d.source) > -1) {
found = 1;
}
}
if (!found) {
cluster.push([d.source, d.target])
}
});
这不是一个美丽的功能,我是第一个同意的:它具有全球性,绝对可以缩短。但这是函数的作用:
创建一个名为cluster
的数组,其中第一对source和target作为(内部)数组,该函数检查所有其他对。如果源或目标存在,则将新值推送到内部数组。如果没有,它会创建一个包含新值的新数组。
然后,您可以通过cluster
数组中的节点索引为节点着色:
.style("fill", function(d, i) {
for (j = 0; j < cluster.length; j++) {
if (cluster[j].indexOf(i) > -1) {
return color(j)
}
}
})
请注意,在此示例中,edges
由nodes数组的索引指定。在你的情况下,你有字符串。
总之,这是演示:
var width = 300;
var height = 200;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var nodes = [{
"id": "foo"
}, {
"id": "bar"
}, {
"id": "baz"
}, {
"id": "foobar"
}, {
"id": "foobaz"
}, {
"id": "barbar"
}, {
"id": "barbaz"
}];
var edges = [{
"source": 0,
"target": 1
}, {
"source": 0,
"target": 2
}, {
"source": 0,
"target": 3
}, {
"source": 1,
"target": 3
}, {
"source": 4,
"target": 5
}, {
"source": 4,
"target": 6
}, {
"source": 5,
"target": 6
}];
var cluster = [
[edges[0].source, edges[0].target]
];
var found;
edges.forEach(d => {
found = 0;
for (var i = 0; i < cluster.length; i++) {
if (cluster[i].indexOf(d.source) > -1 && cluster[i].indexOf(d.target) < 0) {
cluster[i].push(d.target);
found = 1;
} else if (cluster[i].indexOf(d.target) > -1 && cluster[i].indexOf(d.source) < 0) {
cluster[i].push(d.source);
found = 1;
} else if (cluster[i].indexOf(d.target) > -1 && cluster[i].indexOf(d.source) > -1) {
found = 1;
}
}
if (!found) {
cluster.push([d.source, d.target])
}
});
var force = d3.forceSimulation()
.force("link", d3.forceLink()
.id(function(d, i) {
return i
}))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
var myedges = svg.selectAll("line")
.data(edges)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 1);
var color = d3.scaleOrdinal(d3.schemeCategory10);
var mynodes = svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("r", 10)
.style("fill", function(d, i) {
for (j = 0; j < cluster.length; j++) {
if (cluster[j].indexOf(i) > -1) {
return color(j)
}
}
})
.call(d3.drag());
force.nodes(nodes);
force.force("link")
.links(edges);
force.on("tick", function() { //update the position of lines
myedges.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;
})
mynodes.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
});
<script src="https://d3js.org/d3.v4.min.js"></script>