Javascript D3.js如何将强制布局的节点安排在圆上

Javascript D3.js如何将强制布局的节点安排在圆上,javascript,d3.js,force-layout,Javascript,D3.js,Force Layout,我已经开发了一个部队布局来表示社会群体之间的关系。现在,我想让节点分布在一个圆圈中,链接将它们连接起来。最好的方法是什么 代码的完整版本(没有数据)在这里 (为什么我也必须在这里包含代码?这里是主要部分) 这是: 此网络图使用D3 force布局来绘制节点和链接,但我们没有使用D3.force()来查找最佳节点位置,而是绘制了一条不可见的圆弧,并沿圆周均匀放置节点 JS-Bin line.node-link,path.node-link{ 填充:无; 笔画:黑色 } 圆节点{ 填充物:白色;

我已经开发了一个部队布局来表示社会群体之间的关系。现在,我想让节点分布在一个圆圈中,链接将它们连接起来。最好的方法是什么

代码的完整版本(没有数据)在这里 (为什么我也必须在这里包含代码?这里是主要部分)

这是:

此网络图使用D3 force布局来绘制节点和链接,但我们没有使用D3.force()来查找最佳节点位置,而是绘制了一条不可见的圆弧,并沿圆周均匀放置节点


JS-Bin
line.node-link,path.node-link{
填充:无;
笔画:黑色
}
圆节点{
填充物:白色;
笔画:黑色
}
圆。节点+文本{
文本锚定:中间;
}
正文{
字体系列:无衬线;
指针事件:无;
}
//随机节点数(除非更改节点直径,否则在>25时会变得拥挤)
var-num=20;
//返回0和num之间的随机整数
函数getRandomInt(){返回Math.floor(Math.random()*(num));}
//节点返回一个{id:1,fixed:true}的[列表]
var nodes=d3.range(num.map)(函数(d){return{id:d};});
//links返回{source:0,target:1}的[list](值指节点的标记)
var links=d3.range(num).map(函数(d){return{source:getRandomInt(),target:getRandomInt()};});
可变宽度=500,
高度=500;
var-force=d3.layout.force()
.节点(节点)
.链接(links)
.尺寸([宽度、高度]);
//沿圆弧均匀地分隔节点
var circleCoord=函数(节点、索引、num_节点){
var percentage=circle.node().getTotalLength();
var pointAtLength=函数(l){return circle.node().getPointAtLength(l)};
var sectionLength=(周长)/num_节点;
var位置=截面长度*索引+截面长度/2;
返回点长度(圆周位置)
}
//淡出未连接到节点d的线
var是连接的=函数(d,不透明度){
lines.transition()样式(“笔划不透明度”,函数(o){
返回o.source==d | | o.target==d?1:不透明度;
});
}
var svg=d3.选择(“正文”).追加(“svg”)
.attr(“宽度”,宽度)
.attr(“高度”,高度);
//用于放置节点的不可见圆
//它实际上是两个圆弧,所以我们可以使用getPointAtLength()和getTotalLength()方法
var dim=宽度-80
var circle=svg.append(“路径”)
.attr(“d”、“M 40、+(dim/2+40)+“a”+dim/2+”、“+dim/2+”0 1,0“+dim+”、0 a“+dim/2+”、“+dim/2+”0 1,0“+dim*-1+”,0”)
.style(“填充”和“#f5”);
force.start();
//设置容器节点的坐标
forEach(函数(n,i){
var coord=circleCoord(n,i,nodes.length)
n、 x=坐标x
n、 y=坐标y
});
//将此链接用于直线链接。。。
//var lines=svg.selectAll(“line.node链接”)
//.data(links).enter().append(“行”)
//.attr(“类”、“节点链接”)
//.attr(“x1”,函数(d){返回d.source.x;})
//.attr(“y1”,函数(d){返回d.source.y;})
//.attr(“x2”,函数(d){返回d.target.x;})
//.attr(“y2”,函数(d){返回d.target.y;});
//…或将此链接用于曲线链接
var lines=svg.selectAll(“path.node链接”)
.data(links).enter().append(“路径”)
.attr(“类”、“节点链接”)
.attr(“d”,函数(d){
变量dx=d.target.x-d.source.x,
dy=d.target.y-d.source.y,
dr=Math.sqrt(dx*dx+dy*dy);
返回“M”+
d、 source.x+“,”+
d、 source.y+“A”+
dr+“,“+dr+”0,1”+
d、 target.x+“,”+
d、 目标.y;
});
var gnodes=svg.selectAll('g.gnode')
.data(节点).enter().append('g')
.attr(“转换”,函数(d){
返回“translate(“+d.x+”,“+d.y+”)
})
.classed('gnode',true);
var node=gnodes.append(“圆”)
.attr(“r”,25)
.attr(“类”、“节点”)
.on(“鼠标指针”,功能(d){
是否已连接(d,0.1)
node.transition().duration(100).attr(“r”,25)
d3.选择(this).transition().duration(100).attr(“r”,30)
})
.on(“鼠标移动”,功能(d){
node.transition().duration(100).attr(“r”,25);
是连接的(d,1);
});  
var labels=gnodes.append(“文本”)
.attr(“dy”,4)
.text(函数(d){return d.id})

听起来部队布局不合适。也许是a?我查看了径向树,但似乎因为它们是用于层次结构的,所以它不支持我需要的多对多关系。(我可能错了。)好吧,但无论如何,正如你所知道的那样,原力的布局是不合适的。出于好奇,你有没有做到这一点?我遇到了一个类似的问题。我测试了D3PLUS,但也不完美
d3.json("/relationships?nocache=" + (new Date()).getTime(),function(error,members){
   var links=members.organizations.map(function(members) {
      return members.member;
      });

var nodes = {};

links.forEach(function(link) {
  link.source = nodes[link.xsource] || (nodes[link.xsource] = {source: link.xsource, name: link.xsource, category: link.categorysource, path: link.pathsource, desc: link.descsource, title: link.titlesource});
  link.target = nodes[link.xtarget] || (nodes[link.xtarget] = {target: link.xtarget, name: link.xtarget, category: link.categorytarget, path: link.pathtarget, desc: link.desctarget, title: link.titletarget});
});

force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.charge(-120)
.linkDistance(function() {return (Math.random() * 200) + 100;})
.linkStrength(0.5)
.on("tick", tick)
.start();

var link = svg.selectAll(".link")
.data(force.links())
.enter().append("line")
.attr("class", "link");

var node_drag = d3.behavior.drag()
    .on("dragstart", dragstart)
    .on("drag", dragmove)
    .on("dragend", dragend);

var loading = svg.append("text")
.attr("x", width / 2)
.attr("y", height / 2)
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text("Simulating. One moment please…");

function dragstart(d, i) {
    force.stop() // stops the force auto positioning before you start dragging
}

function dragmove(d, i) {
    d.px += d3.event.dx;
    d.py += d3.event.dy;
    d.x += d3.event.dx;
    d.y += d3.event.dy; 
    tick(); // this is the key to make it work together with updating both px,py,x,y on d !
}

function dragend(d, i) {
    d.fixed = true; // of course set the node to fixed so the force doesn't include the node in its auto positioning stuff
    tick();
    force.resume();
};

var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", clickAlert)
.call(node_drag);

node.append("circle")
.attr("r", 8)
.style("fill", function(d) { 
   return categoryColour [d.category];
   })  

// add an image marker
node.append("image")
  .attr("x",-8)
  .attr("y",-8)
  .attr("width", 16)
  .attr("height", 16)
  .attr("xlink:href", function(d) {
      return categoryImage [d.category]
   })
  .on("click", clickAlert)
  .style("cursor", "pointer")

node.append("text")
  .attr("x", 12)
  .attr("dy", ".35em")
  .text(function(d) { 
      return d.name; 
      });
// Use a timeout to allow the rest of the page to load first.
setTimeout(function() {

// Run the layout a fixed number of times.
// The ideal number of times scales with graph complexity.
force.start();
for (var i = n * n; i > 0; --i) force.tick();
force.stop();

svg.selectAll("line")
.data(links)
.enter().append("line")
.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.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 4.5);

loading.remove();
}, 10);

function tick() {
link
  .attr("x1", function(d) { 
       return d.source.x + xadj; })
  .attr("y1", function(d) { 
       return d.source.y + yadj; })
  .attr("x2", function(d) { 
       return d.target.x +xadj; })
  .attr("y2", function(d) { 
       return d.target.y +yadj; });
node
  .attr("transform", function(d) { 
       return "translate(" + (d.x + xadj) + "," + (d.y + yadj) + ")"; 
  });
};

function mouseover() {
 d3.select(this).select("circle").transition()
  .duration(750)
  .attr("r", 16);
d3.select(this).select("text")
   .attr("font-size","34px")
   .style("font-weight", "bold");
};

function mouseout() {
d3.select(this).select("circle").transition()
 .duration(750)
 .attr("r", 8);
d3.select(this).select("text")
  .attr("font-size","12px")
  .style("font-weight", "normal");
};
}) // end json
<!DOCTYPE html>
<html>
<head>
<script src="http://d3js.org/d3.v3.min.js"></script>
  <meta charset="utf-8">
  <title>JS Bin</title>

  <style>
  line.node-link, path.node-link {
    fill: none;
    stroke: black
  }
  circle.node {
    fill: white;
    stroke: black
  }
  circle.node+text {
    text-anchor: middle;
  }
  text {
    font-family: sans-serif;
    pointer-events: none;
  }

  </style>
</head>
<body>
<script type="text/javascript">
// number of random nodes (gets crowded at >25 unless you change node diameter)
var num = 20;

// returns random int between 0 and num
function getRandomInt() {return Math.floor(Math.random() * (num));}

// nodes returns a [list] of {id: 1, fixed:true}
var nodes = d3.range(num).map(function(d) { return {id: d}; });

// links returns a [list] of {source: 0, target: 1} (values refer to indicies of nodes)
var links = d3.range(num).map(function(d) { return {source: getRandomInt(), target: getRandomInt()}; });

var width = 500,
    height = 500;

var force = d3.layout.force()
    .nodes(nodes)
    .links(links)
    .size([width, height]);

// evenly spaces nodes along arc
var circleCoord = function(node, index, num_nodes){
    var circumference = circle.node().getTotalLength();
    var pointAtLength = function(l){return circle.node().getPointAtLength(l)};
    var sectionLength = (circumference)/num_nodes;
    var position = sectionLength*index+sectionLength/2;
    return pointAtLength(circumference-position)
}

// fades out lines that aren't connected to node d
var is_connected = function(d, opacity) {
    lines.transition().style("stroke-opacity", function(o) {
        return o.source === d || o.target === d ? 1 : opacity;
    });
}

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


// invisible circle for placing nodes
// it's actually two arcs so we can use the getPointAtLength() and getTotalLength() methods
var dim = width-80
var circle = svg.append("path")
    .attr("d", "M 40, "+(dim/2+40)+" a "+dim/2+","+dim/2+" 0 1,0 "+dim+",0 a "+dim/2+","+dim/2+" 0 1,0 "+dim*-1+",0")
    .style("fill", "#f5f5f5");

force.start();

// set coordinates for container nodes
nodes.forEach(function(n, i) {
    var coord = circleCoord(n, i, nodes.length)
    n.x = coord.x
    n.y = coord.y
});

// use this one for straight line links...
// var lines = svg.selectAll("line.node-link")
//   .data(links).enter().append("line")
//     .attr("class", "node-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; });

// ...or use this one for curved line links
var lines = svg.selectAll("path.node-link")
    .data(links).enter().append("path")
    .attr("class", "node-link")
    .attr("d", function(d) {
        var dx = d.target.x - d.source.x,
            dy = d.target.y - d.source.y,
            dr = Math.sqrt(dx * dx + dy * dy);
        return "M" + 
            d.source.x + "," + 
            d.source.y + "A" + 
            dr + "," + dr + " 0 0,1 " + 
            d.target.x + "," + 
            d.target.y;
    });

var gnodes = svg.selectAll('g.gnode')
    .data(nodes).enter().append('g')
    .attr("transform", function(d) {
        return "translate("+d.x+","+d.y+")"
    })
    .classed('gnode', true);

var node = gnodes.append("circle")
    .attr("r", 25)
    .attr("class", "node")
    .on("mouseenter", function(d) {
        is_connected(d, 0.1)
        node.transition().duration(100).attr("r", 25)
        d3.select(this).transition().duration(100).attr("r", 30)
    })
    .on("mouseleave", function(d) {
        node.transition().duration(100).attr("r", 25);
        is_connected(d, 1);
    });  

var labels = gnodes.append("text")
    .attr("dy", 4)
    .text(function(d){return d.id})
</script>

</body>
</html>