D3 Circle-Packing Clear Labeling Solution

时间:2015-07-25 07:00:20

标签: javascript d3.js circle-pack

在d3的打包布局中是否有办法手动设置子节点的半径,其大小相对于父半径,然后让其他子节点根据剩余空间设置其半径并使用现有的“儿童人数的大小“?

我想做的是: 1.为每个节点添加一个节点到children数组,其名称与父节点相同 2.将此额外子项的半径设置为具有足够大的半径以包含文本并确保其邻居不重叠 3.在此额外节点上将fill和stroke设置为none 4.在此额外节点上将css的单击交互设置为none 5.仅使用这些额外节点来显示“他们的”名称(也就是他们的父母的名字)

结果将是一个包装圆圈,其标签有一个特别指定的空间。如果没有手动设置额外子节点的半径,这不起作用,因为它的大小是根据子节点数自动确定的。 (添加未填充/未缩放的子节点以进行补偿是非常低效的。第一次黑客攻击的第二次攻击是不值得的,我不认为)

2 个答案:

答案 0 :(得分:1)

请检查这是否对您有所帮助。红色背景与“额外”课程的圈子是带有父母姓名的额外圈子。

jsbin link

var root = {
 "name": "flare",
 "children": [
  {
   "name": "analytics",
   "children": [
    {
     "name": "cluster",
     "children": [
      {"name": "AgglomerativeCluster", "size": 3938},
      {"name": "CommunityStructure", "size": 3812},
      {"name": "HierarchicalCluster", "size": 6714},
      {"name": "MergeEdge", "size": 743}
     ]
    },
    {
     "name": "graph",
     "children": [
      {"name": "BetweennessCentrality", "size": 3534},
      {"name": "LinkDistance", "size": 5731},
      {"name": "MaxFlowMinCut", "size": 7840},
      {"name": "ShortestPaths", "size": 5914},
      {"name": "SpanningTree", "size": 3416}
     ]
    },
    {
     "name": "optimization",
     "children": [
      {"name": "AspectRatioBanker", "size": 7074}
     ]
    }
   ]
  },
  {
   "name": "animate",
   "children": [
    {"name": "Easing", "size": 17010},
    {"name": "FunctionSequence", "size": 5842},
    {
     "name": "interpolate",
     "children": [
      {"name": "ArrayInterpolator", "size": 1983},
      {"name": "ColorInterpolator", "size": 2047},
      {"name": "DateInterpolator", "size": 1375},
      {"name": "Interpolator", "size": 8746},
      {"name": "MatrixInterpolator", "size": 2202},
      {"name": "NumberInterpolator", "size": 1382},
      {"name": "ObjectInterpolator", "size": 1629},
      {"name": "PointInterpolator", "size": 1675},
      {"name": "RectangleInterpolator", "size": 2042}
     ]
    },
    {"name": "ISchedulable", "size": 1041},
    {"name": "Parallel", "size": 5176},
    {"name": "Pause", "size": 449},
    {"name": "Scheduler", "size": 5593},
    {"name": "Sequence", "size": 5534},
    {"name": "Transition", "size": 9201},
    {"name": "Transitioner", "size": 19975},
    {"name": "TransitionEvent", "size": 1116},
    {"name": "Tween", "size": 6006}
   ]
  },
 ]
};
var addExtraNode = function(item, percentSize){
  var percentSizeOfNode = percentSize || 60; //if not given it will occupy 60 percent of the space
  if(!item.children){
    return;
  }
  var totalChildSize = 0;
  item.children.forEach(function(citm, index){
    totalChildSize = totalChildSize + citm.size;
  })
  
  var nodeSize = (percentSizeOfNode / 50) * totalChildSize;
  var name = 'NAME: '+item.name;
  item.children.push({
    'name': name,
    'size': nodeSize,
    'isextra':true
  })
  
  item.children.forEach(function(citm, index){
    if(citm.children){
      addExtraNode(citm, percentSize);
    }
  })
};

addExtraNode(root, 55);

var diameter = 500,
    format = d3.format(",d");

var pack = d3.layout.pack()
    .size([diameter - 4, diameter - 4])
    .value(function(d) { return d.size; });

var svg = d3.select("body").append("svg")
    .attr("width", diameter)
    .attr("height", diameter)
  .append("g")
    .attr("transform", "translate(2,2)");


 
var node = svg.datum(root).selectAll(".node")
    .data(pack.nodes)
	.enter().append("g")
    .attr("class", function(d) {
      
      if(d.isextra){
        return 'extra';
      }
      return d.children ? "node" : "leaf node"; })
    .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

node.append("title")
    .text(function(d) { return d.name + (d.children ? "" : ": " + format(d.size)); });

node.append("circle")
    .attr("r", function(d) { return d.r; });

node.filter(function(d) { return !d.children; }).append("text")
    .attr("dy", ".3em")
    .style("text-anchor", "middle")
    .text(function(d) { return d.name.substring(0, d.r / 3); });
circle {
  fill: rgb(31, 119, 180);
  fill-opacity: .25;
  stroke: rgb(31, 119, 180);
  stroke-width: 1px;
}

.leaf circle {
  fill: #ff7f0e;
  fill-opacity: 1;
}

text {
  font: 10px sans-serif;
}
.extra circle{
  fill:red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

答案 1 :(得分:0)

这个答案是我现在使用的解决方案,并以Ganesh提供的解决方案为基础。

Ganesh解决方案摘要:

  • 引入一个运行数据的函数,并在每个节点的子数组中注入一个额外的节点,除非它检查叶子节点。

我使用Ganesh解决方案的剩余问题:

  • 并非所有父节点都有值,因此它们没有标签
  • 没有关于如何计算节点值的指示,它们已在json数据中提供
  • 为标签创建的额外节点太小,因为它的邻居总是为了容纳它的孩子而增长。

解决方案和主要发现:

  • 值基于节点子节点的总和来确定
  • 我的示例中的叶节点的值为100
  • 根节点的值大约为31,700,这大致准确,因为它总共有316个孩子(直接和传递),自己计算
  • 因此,我更新了我的数据,以便每个节点还包括其子树的大小
  • 我的价值函数的最终版本基于Ganesh的解决方案,如下所示:

            var pack = d3.layout.pack()
            .value(function(d){ 
                if(d.isExtra){ //the property added to injected label nodes
                    d.value = d.parent.treeSize*100 //treeSize = size of subTree
                    return d.value;
                }
                else{
                    d.value = d.treeSize*100;
                    return d.value;
                }
            })
            .size([width, height -100 ])
    

重要的是要注意,虽然我引入的新节点的值等于它的父节点乘以100,但这并不意味着它占用了圆的空间的100倍。这是因为父母的价值最终是儿童价值的总和,这意味着任何有孩子的节点都会看到总价值的增加。将标签节点的treeSize乘以显着更高的数字仅提供相应圆周大小的对数增加,同时在叶节点大小(100)和其余圆圈之间引入越来越大的间隙。