【问题标题】:D3 Circle-Packing Clear Labeling SolutionD3 圆形包装透明标签解决方案
【发布时间】:2015-10-15 21:55:40
【问题描述】:

在 d3 的打包布局中是否有一种方法可以手动设置子节点的半径,其大小相对于父半径,然后让其他子节点根据剩余空间并使用现有的“孩子的大小”?

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

结果将是一个紧凑的圆圈,其中有一个专门为标签指定的空间。如果没有手动设置额外子节点的半径,这将不起作用,因为它的大小是根据子节点的数量自动确定的。 (添加未填充/未描边的子节点来补偿效率极低。我不认为第一次 hack 的第二次 hack 不值得)

【问题讨论】:

    标签: javascript d3.js circle-pack


    【解决方案1】:

    请检查这是否对您有帮助。带有“extra”类的红色背景圆圈是带有父母姓名的额外圆圈。

    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 你是个天才 好先生!我希望我能给你一杯啤酒:)
    • 在我能够将您的答案应用于我的情况之前,我还有一个问题,这与 pack.value 函数/您的尺寸参数有关。我对d3还是很陌生,所以你能帮我1)更好地理解价值函数的作用吗? 2) 了解您是如何得出您使用的尺寸值的。我知道 d3 的文档说值是决定圆圈大小的因素,但我不知道确切的相关性是什么。我尝试调整您答案中的部分或全部大小,但它不会根据我对它的工作原理的任何假设来调整大小。
    • 另外,这行代码是做什么的? node.append("title") .text(function(d) { return d.name + (d.children ? "" : ": " + format(d.size)); });
    • 该代码用于在节点上显示工具提示。尝试将鼠标悬停在您将显示名称:大小的圆圈上。它检查节点是否有子节点,它显示为空白,它的叶节点显示名称前面的大小。所以只有叶节点显示名称的大小。
    • 如果你发现我的函数调用 addExtraNode(root, 55);这里 55 是你想要的百分比,如果你改变这个数字,额外节点的大小将会改变。 d3 包布局的 .value 函数采用返回与元素关联的数据对象的特定列的函数。在我们的例子中,它的“大小”。该函数由 d3 评估以确定节点的直径。
    【解决方案2】:

    这个答案是我现在使用的解决方案,并建立在 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) 和其余圆之间引入越来越大的差距。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-04
      • 1970-01-01
      • 2023-03-24
      • 1970-01-01
      相关资源
      最近更新 更多