D3 Circle-Packing 清除标签解决方案

D3 Circle-Packing Clear Labeling Solution

在 d3 的打包布局中有没有办法手动设置 child 节点的半径,其大小相对于 parent 半径,然后让另一个 child ren 根据剩余的 space 并使用现有的 "size by number of children"?

设置它们的半径

我想做的是: 1. 为每个节点添加一个与 parent 同名的节点到 children 数组 2. 将这个额外 child 的半径设置为足够大的半径以包含文本并确保其邻居不重叠 3. 在这个额外节点上将填充和描边设置为 none 4. 在此额外节点上通过 css 将点击交互设置为 none 5. 只使用这些额外的节点来显示 "their" 的名字(也就是他们的 parent 的名字)

结果将是一个带有专门指定的 space 标签的填充圆。如果没有手动设置额外 child 节点的半径,这是行不通的,因为它的大小是根据 children 的数量自动确定的。 (添加 children unfilled/unstroked 个节点来补偿是非常低效的。我不认为第一次 hack 的第二次 hack 是不值得的)

请检查这是否有帮助 you.The 带有 class 的红色背景圆圈 'extra' 是带有 parent 名字的额外圆圈。

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>

这个答案是我目前使用的解决方案,它建立在 Ganesh 提供的解决方案之上。

Ganesh 的解决方案总结:

  • 引入一个函数来运行您的数据并将一个额外的节点注入每个节点的 child 数组,但检查叶节点时除外。

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

  • 并非所有 parent 节点都有值,因此它们没有标签
  • 没有说明如何计算节点值,它们已经在 json 数据中提供
  • 为标签创建的额外节点太小,因为它的邻居总是增长以容纳它的 children。

解决方案和主要发现:

  • 值是根据一个节点children
  • 的总和来确定的
  • 我示例中的叶节点的值为 100
  • 根节点的值大约为 31,700,这大致是准确的,因为它总共有 316 children(直接和传递),算上它自己
  • 因此,我更新了我的数据,以便每个节点还包括其子树的大小
  • 我的价值函数的最终版本,基于 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 ])
    

这里需要注意的是,虽然我引入的新节点的值等于 parent 节点乘以 100,但这并不意味着它需要 space 的 100 倍圈子。这是因为 parent 的值最终是它的 children 值的总和,这意味着任何具有 child 的节点的总值都会增加。将标签节点的 treeSize 乘以明显更高的数字只会提供相应圆大小的对数增加,同时在叶节点大小 (100) 和其余圆之间引入越来越大的差距。